DEV Community

Julian Garamendy
Julian Garamendy

Posted on • Updated on • Originally published at juliangaramendy.dev

Managing Remote Data with SWR

In this series, instead of using a state-management library or proposing a one-size-fits-all solution, we start from the bare minimum and we build up our state management as we need it.


In the previous articles we were storing the loaded data within React, in a useState hook. But since then SWR was released (Oct 2019).

I first learned about SWR thanks to a video tutorial by Leigh Halliday: "React Data Fetching with Hooks using SWR", and I thought it was interesting enough that I could try it on a small internal project at work.

But a few weeks later a Twitter thread took me to this article; something clicked in my head and I realised I wasn't just looking for an excuse to try SWR.

No. I had been doing it wrong all along!

The Scream, by Edvard Munch - National Gallery of Norway

I was storing my remotely fetched data in useReducer or useState and manually mutating (or via a reducer), and then maybe reloading from the server in some cases, but not in others. And I was using React Context to make the data available to unrelated components in my app.

SWR makes this easier and better.

SWR stores the fetched data in a static cache. Therefore there's no need to use React Context to share the data with other components. And all components fetching the same data are updated when the data changes.

I refactored my SPA to use SWR and that resulted in a much simpler application logic. In addition, we now benefit from all the nice features that come with SWR such as "focus revalidation" and "refetch on interval".

Let's refactor our example from the previous three articles to use SWR.

Before SWR

Our demo app before SWR is what we got after our third article. (see repo)

Install SWR

yarn add swr
Enter fullscreen mode Exit fullscreen mode

Refactoring our custom hook

In our demo app we have a custom useFetchedGames hook that loads the games using the useAsyncFunction hook, and then stores them using useState to provida a way to locally mutate the data.

const useFetchedGames = () => {
  const [fetchedGames, error, isPending] = useAsyncFunction(getGames, emptyList);

  const [games, setGames] = React.useState(emptyList);
  React.useEffect(() => {
    setGames(fetchedGames);
  }, [fetchedGames]);

  return { games, setGames, error, isPending };
};
Enter fullscreen mode Exit fullscreen mode

The problem with this approach is:

  1. The list of games is stored twice: first in a useState hook inside useAsyncFunction, and then in a new useState hook.
  2. If the list of games is updated on the server, we never reload it. Here's where SWR shines.

We're going to refactor useFetchedGames to use SWR instead of useState.

const useFetchedGames = () => {
  const { data, error, mutate } = useSWR('getGames', getGames); 

  const games = data || []
  const isPending = !data
  const setGames = mutate

  return { games, setGames, error, isPending };
};
Enter fullscreen mode Exit fullscreen mode

The full diff can be found in this git commit.

Note the "getGames" string literal, just before the getGames function. This is a key to help SWR identify our request. In our case it can be anything as long as it is unique for this resource (the list of games). There's a even simpler way. You can find it in the docs.

Removing React Context

Now that we're using useSWR we don't need a React context, its provider, nor the useContext hook.

In the demo project we make our components consume the useGames hook directly, instead of the useGamesContext one.

For example, GameGrid.tsx:

- import { useGamesContext } from '../GamesContext';
+ import { useGames } from '../useGames';

  export const GameGrid = () => {
-   const { games, error, isPending, markAsFinished } = useGamesContext();
+   const { games, error, isPending, markAsFinished } = useGames();

    return (
      <div className="gamegrid">
Enter fullscreen mode Exit fullscreen mode

You can see the complete diff in this git commit.

Conclusion

With this small refactoring, our app has less code to maintain and we benefit from other great SWR features:

  • Revalidate on focus.
  • Revalidate on interval. (optional)
  • Revalidate on reconnect.
  • Retry on error.

I think Zeit's SWR (or a similar library) is a much better solution than storing fetched data in a React component using useState or useReducer.

I continue to store my application's UI state using custom hooks that use useReducer and useState but for remote data, I prefer to store it in a cache.

Please let me know what you think in the comments below.

Oldest comments (3)

Collapse
 
tlacalv profile image
Tlacaelel Leon Villaseñor

Maybe a little too late, I'm currently working on a project and looking to implement this approach to retrieve data. however, I already implemented the Auth side of the application using custom hooks and a context, is it necessary to try and implement SWR for that too?

Collapse
 
juliang profile image
Julian Garamendy

You can definitely have authentication handled separately.
You don't need to use SWR for everything. Your can start with 1 query.

Collapse
 
liamgsmith profile image
Liam Smith

I realise this is a two year old post. I have created an account specifically to say THANK YOU for this.
This is the first article I've been able to find with the method of abstracting and then referencing the useSWR call out to it's own file for repeatability across components.
I appreciate it's probably basic stuff but as a beginner this is that next level of complexity from the basics which I have not been able to find any info on.
THANK YOU!!