Abdulazeez Abdulazeez Adeshina Software enthusiast, writer, food lover, and hacker.

Caching clash: useSWR() vs. react-query

8 min read 2312

React Query vs Useswr

Editor’s note: This article was last updated 29 March 2022 to reference React Query v3 as its point of comparison. 

To store data in React applications, developers typically use state. But what happens when the app is reloaded? The state returns to point blank, but it is filled up when the component mounts, usually in the useEffect() Hook or the componentDidMount() method.

Most often, the data that was loaded into the application’s state is from an external source and repeatedly retrieved. But, imagine a scenario in which the data source crashes or the network becomes slow. As a result, our app would return a blank page without any data.

Luckily, there are two libraries that handle retrieving data into the cache without dealing with state, React Query and SWR. In this article, we’ll build a simple app to showcase the features of SWR and then compare SWR to React Query.

To follow along with this tutorial, you should be familiar with React Query, as well as JavaScript, React, React Suspense, and JSX.

The code for this article can be found at this GitHub repo. Let’s get started!

Table of contents

What is SWR?

SWR, an initialism derived from stale-while-revalidate, is a React Hook library from Vercel, formerly called ZEIT, that retrieves data from an external source API, stores the data into cache, then renders the data. In this article, we’ll explore some of SWR’s features like data fetching and Suspense mode.

You can install the SWR library from either Yarn or npm as follows:

npm i swr

// or

yarn add swr

What is useSWR()?

SWR’s useSWR(key, fetcher, options) is a Hook that retrieves data asynchronously from a URL with the help of a fetcher function. The URL and fetcher function are both passed as arguments to the Hook.

The key argument is the URL in string format, and the fetcher is either a function declared in the global configuration, a predefined custom function, or a function defined as the useSWR() argument.

We made a custom demo for .
No really. Click here to check it out.

By default, useSWR() returns the data received, a validation request state, a manual revalidate argument, and an error, if there are any. You can easily do this by setting the Hook to a destructurable object variable as follows:

const { data, isValidating, revalidate, error } = useSWR(key, fetcher)

useSWR() features

Data fetching is useSWR()’s primary feature. Just like React Query, data fetching is done only once when the component is to render data, unlike the traditional method of loading data every time the component is rendered.

Global configuration

useSWR() has a global configuration context provider that gives access to all of its options, so you can leave the options argument in the useSWR() Hook blank. Below is an example of the global configuration in use:

import useSWR, { SWRConfig } from 'swr'

function Example () {
  const { data } = useSWR('http://book-api.com')
  const { data: latest } = useSWR('http://latest-books-api.com')
}

function App () {
  return (
    <SWRConfig 
      value={{
        refreshInterval: 3000,
        fetcher: (...args) => fetch(...args).then(res => res.json())
      }}
    >
      <Example />
    </SWRConfig>
  )
}

In the code above, the global configuration provider component <SWRConfig /> gives us the opportunity to define the fetcher function so we don’t have to add it as an argument every time in our useSWR() Hook. The fetcher defined in the global configuration provider is universal for the components consuming it, i.e., wrapped under it.

Although this isn’t a mandatory step when using the useSWR() Hook, it is the best approach provided the app maintains data retrieval homogeneity.

Fetching data

Fetching data with useSWR() is pretty straightforward. In our demo, first, we define our sample component, let’s call it RocketLauncher, and we store the result from our useSWR() into two destructurable variables:

function RocketLauncher() {
  const { data, error }  = useSWR('http://rocket-time.api', fetcher)

  return  (
    <>

    </>
  )
}

const fetcher = url => fetch(url).then(r => r.json())

The destructurable variables contain the following:

  1. The data variable holds the data returned from the fetcher function
  2. The error variable holds whatever error is sent back from the Hook

Next, we render the data returned:

...
<>
 { error ? (
      <b>There's an error: {error.message}</b>
    ) : data ? (
      <ul>
        {data.map(recipe => (
          <li key={rocket.id}>{rocket.name}</li>
        ))}
      </ul>
    ) : null }
</>
...

The block of code above renders the data retrieved from useSWR() if there’s no error returned; otherwise, a blank page is returned. In the next section, we’ll see this in action.

Building the app

To demonstrate how useSWR() works, let’s rebuild a recipe app formerly built with React Query from this article. Since we’re only rebuilding the app with a different library, you can copy the setup process from the previous article. Let’s get started!

Frontend components

To build the app’s frontend, we’ll use the global configuration so we don’t have to call the fetcher function every time. We’ll also enable Suspense mode in the global configuration settings.

index.jsx

import React, { lazy } from "react";
import ReactDOM from "react-dom";

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement)

This is a basic render file. Next, we import useSWR() and the recipe components since we’ll be writing the main app component, <App />, in the index.jsx file:

import useSWR, { SWRConfig } from "swr";
import fetcher from "./fetch";

const Recipes = lazy(() => import("./components/Recipes"));
const Recipe = lazy(() => import("./components/Recipe"));

We imported useSWR alongside the global configuration context provider. Next, we’ll write our App component:

import { Suspense, Fragment, useState } from "react"

function App () {
  const [activeRecipe, setActiveRecipe] = useState(null);
  return (
    <Fragment>
      <h1>Fast Recipes</h1>
      <hr />
      <SWRConfig
        value={{
          refreshInterval: 3000,
          fetcher: fetcher,
          suspense: true
        }}
      >
        <Suspense fallback={<h1> Loading ...</h1>}>
          {activeRecipe ? (
            <Recipe
              activeRecipe={activeRecipe}
              setActiveRecipe={setActiveRecipe}
            />
          ) : (
            <Recipes setActiveRecipe={setActiveRecipe} />
          )}
        </Suspense>
      </SWRConfig>
    </Fragment>
  );
}

In the code above, we wrap our lazily loaded recipe components under React’s Suspense, which is also wrapped under the global configuration provider, SWRConfig().

The global configuration provider has been equipped with our fetcher function, which we’ll define next, so we don’t have to add the fetcher as an argument to the useSWR() Hook in the Recipe and Recipes components:

fetch.js

The fetch.js file contains the code that retrieves data from the source passed into the useSWR() Hook in JSON format:

import fetch from "unfetch"

const fetcher = url => fetch(url).then(r => r.json())

export default fetcher;

Recipe.jsx

In Recipe.jsx, we’ll start off by importing React Fragment and the SWR library:

import React, { Fragment } from "react";
import useSWR from "swr";

import Button from "./Button";

Next, we’ll write the Recipe component:

export default function Recipe({ activeRecipe, setActiveRecipe }) {
  const { data } = useSWR(
    "http://localhost:8081/" + activeRecipe);
  return (
    <Fragment>
      <Button onClick={() => setActiveRecipe(null)}>Back</Button>
      <h2>ID: {activeRecipe}</h2>
      {data ? (
        <div>
          <p>Title: {data.title}</p>
          <p>Content: {data.content}</p>
        </div>
      ) : null}
      <br />
      <br />
    </Fragment>
  );
}

The Recipe component takes two props, activeRecipe and setActiveRecipe, which are involved with retrieving and rendering data.

The useSWR() Hook is passed the data source URL, and the data to be retrieved is stored in the data variable. Upon being retrieved, the data is rendered, as seen from lines 8 to 13.

The data returned is cached and won’t be retrieved when the app loads again unless there is a change in the data from the source. Essentially, if the data retrieved is the same as what’s in the cache, it renders the cache data; otherwise, it will cache the new data. Next, we’ll write the Recipes component.

Recipes.jsx

The Recipes component is responsible for rendering the list of recipes retrieved from the data source via useSWR(). The code below is responsible for that:

import React from "react";
import useSWR from "swr";
import Button from "./Button";

export default function Recipes({ setActiveRecipe }) {
  const { data: Recipes } = useSWR(`http://localhost:8081`);
  return (
    <div>
      <h2>
        Recipes List        
      </h2>
      {Recipes ? Recipes.map(Recipe => (
        <p key={Recipe.title}>
          {Recipe.title}
          <Button
            onClick={() => {
              setActiveRecipe(Recipe.id);
            }}
          >
            Load Recipe
          </Button>{" "}
        </p>
      )) : 'loading'}
    </div>
  );
}

In the component, we started off by importing React and SWR, enabling us to use the useSWR() Hook.

A loading message is displayed when the data is being fetched. The useSWR() Hook is used to retrieve the list of recipes from the backend.

Next, the data retrieved from SWR is cached, mapped out from its array, and then rendered on the DOM, as seen from lines 12 to 23. The code for the helper component Button is below.

Button.jsx

import React from "react";
export default function Button({ children, timeoutMs = 3000, onClick }) {
  const handleClick = e => {
      onClick(e);
  };
  return (
    <>
      <button onClick={handleClick}>
        {children}
      </button>
    </>
  );
}

Running our app

Next, we’ll preview the app we’ve built so far. We’ll start by running the app first without the backend to verify that a blank page will be displayed when no data is returned. From your terminal, start the React app and the backend in two different terminal consoles:

//React App
npm run start or yarn start

//Backend App
node api.js

Next, open the app on your browser with http://localhost:3000. You should get the same page as the one in the gif below. Feel free to check the recipes one after the other, and reload the app to experience caching:

Fast Recipes Sample Code
useSWR recipes app

If you’ve followed the two articles, you will have noticed that they both perform the same functions: rendering, fetching data, and caching. However, in addition to those basic similarities, there are some differences between the two libraries.

SWR vs. React Query: Similarities

Fetching and caching data

Both React Query and SWR are Hook libraries that fetch data remotely. These two libraries fetch data asynchronously and cache data upon retrieval and, as a result, prevent the continuous retrieval of data from the data source on every app render.

Suspense mode

Both libraries allow the use of React’s Suspense, allowing the app to keep the user updated while the app fetches data via either of the libraries.

Fast and reactive app state

Both libraries improve the load time and responsiveness of an app, especially when rendering data after the first time. This is due to the caching of data, which makes it readily available whenever the app needs it, even when it’s offline.

That said, there is a small difference in load time between useSWR() and React Query. useSWR() comes out on top here, with 628ms to 523ms, as shown in the images below:

Use SWR Load Time
useSWR load time
React Query Load Time
React Query load time

SWR vs. React Query: Differences

Although both applications are remote, data fetching, agnostic Hook libraries, they have their differences. They are written by different authors, after all. Let’s take a look at the limitations and advantages these libraries have over each other.

Global fetcher

Unlike React Query, where we have to call the fetcher as the second argument, SWR enables us to define a global fetcher function in the configuration provider so we don’t have to import or define the fetcher function every time we need to use the useSWR() Hook.

Prefetching data

React Query has an advantage over SWR when it comes to prefetching data, but it requires additional configurations, like writing more functions and mutating them to the useEffect() Hook. In contrast, React Query has a prop handler that lets you prefetch data by setting the data ID and source without extra configurations.

GraphQL support

SWR offers more advantage for modern apps that use GraphQL. Depending on its configuration, GraphQL can be a faster alternative to REST. In cases where a user’s application retrieves data from a GraphQL server, SWR comes in handy.

GraphQL queries can be sent and data received, as demonstrated in this snippet from the SWR library:

import { request } from 'graphql-request'

const API = 'https://api.graph.cool/simple/v1/movies'
const fetcher = query => request(API, query)

function App () {
  const { data, error } = useSWR(
    `{
      Movie(title: "Inception") {
        releaseDate
        actors {
          name
        }
      }
    }`,
    fetcher
  )
  // ...
}

Mutating data

SWR enables you to update data locally while waiting for the remote source to revalidate it.

Conclusion

Both SWR and React Query are great for remote data fetching and can be used in React projects. SWR generally works hand in hand with Next.js, another project from its authors.

However, SWR has the major advantage due to its compatibility with GraphQL and overall speed, which are some of the factors taken into consideration when selecting third-party libraries for mostly large-scale applications.

Therefore, SWR is preferable for large-scale applications or projects that have to do with the distribution of data, while React Query is better for side projects or smaller applications.

In this article, we explored SWR and the useSWR() Hook by rebuilding a recipe app that was previously built with React Query. We also compared SWR and React Query, reviewing their similarities and differences. I hope you enjoyed this tutorial, and be sure to leave a comment if you have any questions.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Abdulazeez Abdulazeez Adeshina Software enthusiast, writer, food lover, and hacker.

9 Replies to “Caching clash: useSWR() vs. react-query”

  1. A few corrections here (I am the author of React Query):

    – React Query is also capable of the GraphQL pattern described. The syntax is only slightly different (the query markup is passed through to the fetcher, where it can then be used in the same exact way. eg.

    “`js
    import { request } from ‘graphql-request’

    const QUERY = `{
    Movie(title: “Inception”) {
    releaseDate
    actors {
    name
    }
    }
    }`

    function App() {
    const { data, error } = useSWR(QUERY, () =>
    request(‘https://api.graph.cool/simple/v1/movies’, QUERY)
    )
    }
    “`

    – React Query is also capable of optimistically mutating data via the setQueryData function (https://github.com/tannerlinsley/react-query#setquerydata)

    – React Query automatically handles garbage collection of stale and unused query data, something that SWR does not do.

  2. More feedback (React Query author here):

    – React Query ships with a query (promise) cancellation API

    – React Query supports declarative updates to query data from mutation responses (useful when doing patch mutations)

    – React Query ships with a useIsFetching hook: https://github.com/tannerlinsley/react-query#useisfetching

    – React Query ships with custom key serializers: https://github.com/tannerlinsley/react-query#custom-query-key-serializers-experimental

    – React Query ships with better support for auto-refreshing of queries when using iframes in your apps.

    – The “speed” of each library is going to be negligible and variable depending on the latency of your requests. There is technically nothing in either library that is making them “faster” or “slower” than each other by any means. I would absolutely profile, measure and gather some better stats on why you believe one is faster than the other to support your claim.

    – SWR does not support GraphQL out of the box any more than React Query does. They merely supply an example of how to do it (I have done the same in my earlier comment above for React Query). I wouldn’t use this as a factor in comparing the technical capabilities of the two libraries, since both are simply promise-based APIs and have nothing to do with GraphQL directly.

    – I designed and built React Query around a Next.js site as well (most of the examples use Next.js to demonstrate how to use the library). I would not use this as a strength, again, simply because there is nothing about SWR’s API that makes it easier to use with Next that React Query.

    – When mentioning that you would want to use one over the other because of memory or large application size, I would still recommend React Query over SWR, given that it comes built-in with single-query and group-query invalidation (something that is less than trivial in SWR), handles garbage collection automatically for unused data (SWR could potentially become a memory leak if many different variations of queries were run over and over in the same window session. Not likely for small to medium apps, but still possible).

  3. React Query 1.0 is now out that takes much of the great things of React Query and makes them even better including:

    – usePaginatedQuery – A dedicated hook for window-like querying of paginated data or cursor-driven batches of data
    – useInfiniteQuery – A dedicated hook for accumulative querying of paginated data or cursor-driven batches of data
    – Synchronous Query Cache Reads/Writes/Upserts/Deletes
    – Improved query key matching for removing and refetching queries
    – External subscriptions to query cache updates
    – Unlimited query key length support
    – Optional Query Variables
    – onSettled callback for query and mutation hooks
    – onSuccess and onError callbacks for mutations
    – Better SSR support via config.initialData
    – config.initialData now supports passing an initializer function, similar to React.useState
    – Query status is now automatically inferred based on initialData (success for non-undefined intitial data, loading for undefined intitial data)
    – Query objects now supply an updatedAt property for making better informed decisions about synchronous cache usage
    – Overall, less over-fetching out of the box when using multiple instances of a query.
    – Added a new config.refetchOnMount that defaults to true and when set to false will disable additional instances of a query to trigger background refetches.
    – More reliable suspense support for prefetched queries and background refetching lifecycles
    – Support for errorBoundaries out of the box when using Suspense for both queries and mutations
    – Added a globalConfig.queryFnParamsFilter function that allows filtering the parameters that get sent to the query function.

    Maybe this article needs another refresh?

  4. Great! I’ll see what can be done or perhaps write a new article on what’s new in react query ( maybe ) & congrats on releasing 1.0 👍🏿

  5. Hi there, thanks for the post. Your section on GraphQL has some very misleading information in it. Specifically:

    > GraphQL is a much faster and more efficient alternative to REST.
    >
    > In REST, you have to query the whole API to get specific data and results, which returns a whole lot of (mostly unused) data, slowing down your app.

    There’s nothing about this that is true inherently. It is true that you can use an API that was not made for your client and that may send data that you do not need. In situations where the API is out of your control (like GitHub’s, for example), there’s an argument for GraphQL. However, if your company controls both the API and the client (as is the case for many readers of your blog, I’m sure), then there’s absolutely nothing inherent about REST that makes it slower than GraphQL.

    API design is API design. You can either do it poorly or not. GraphQL solves some very specific problems for very specific types of companies and situations. It does not do the things that you’ve claimed unless there is already a problem, and, one can bet that a team that introduced the problematic REST API would not have much better luck with avoiding problems using GraphQL.

  6. You’re welcome. If you’re able, it would be great if you updated the article. As people with a voice (and a blog), we have responsibility to the development community to not mislead them and stoke the FUD (Fear, Uncertainty and Doubt) that is already present and misleading people.

    1. Hey, Tanner. We hear you. Would you be willing to write a new post so that we can eliminate the back and forth in the comments?

Leave a Reply