DEV Community

Cover image for “useSwr” The New React Hook for Fetching Remote Data.
Chidi Eze
Chidi Eze

Posted on • Edited on

“useSwr” The New React Hook for Fetching Remote Data.

Introduction

We’ll be looking at a new way to fetch data in React projects in this post. This is a hook built by Vercel, called SWR used for remote data fetching with other features, such as caching, pagination, error handling, auto revalidation, mutation and so on. We’ll be building a Random Store App, using SWR hook to get data from FakeStore API.

Originally we would make our data fetching in a top-level component and pass the returned data as props to the components down the tree. Using this method would be challenging to maintain our code if we build a more extensive application requiring many data dependencies to be passed to the page. React’s Context helps this course a lot, but SWR provides us with a cleaner and declarative way of fetching data that even parent components won’t worry about knowing which data the child components need or passing them. With SWR, data are bound to the component that needs them.

Sandbox

Fork or preview the finished version of this project here, in CodeSandBox.

Prerequisites

To follow this project correctly, you should have basic knowledge of react.js and have node.js installed on your computer.

Setting up React.js Application

So create a folder anywhere in your computer, open the folder with your terminal or code editor and run the following command to create a react app and install axios and swr packages:

npx create-react-app . #to create a react application
npm install axios swr # to install axios and swr packages
Enter fullscreen mode Exit fullscreen mode

Clean up your react app and run npm start to start the application, on the browser the application should run on localhost:3000.

Get Started

Head straight inside App.js and replace every code with the following code snippets.

    //App.js
    import "./App.css";
    function App() {
      return (
        <div className="App">
          <h3>Hello there!</h3>
        </div>
      );
    }
    export default App; 
Enter fullscreen mode Exit fullscreen mode

If you save and go over to the browser, it should look like below:

setting-up

Now let’s go over to the code editor, and inside the src folder, create another folder, Components and inside it, create two files Header.js and Products.js. Inside the Header component, paste the following snippets:

    //Header.js
    import React from "react";
    export default function Header() {
      return (
        <div>
          <header>
            <h1 className="brand">
              <strong>Random-Store</strong>{" "}
            </h1>
            <div className="side-nav">
              <ul>
                <li>Blog</li>
                <li>News</li>
                <li>About</li>
              </ul>
            </div>
          </header>
        </div>
      );
    }
Enter fullscreen mode Exit fullscreen mode

If you want, get the CSS used in this project here, or you style it how you want. Import the Header component inside App.js and render it like below:

    //App.js
    import "./App.css";
    import Header from "./Components/Header";
    function App() {
      return (
        <div className="App">
          <Header />
        </div>
      );
    }
    export default App;
Enter fullscreen mode Exit fullscreen mode

If you check, your browser should look like below if you got the CSS.

header

Our project is already taking shape; next, let’s dive in and explore the SWR features by fetching our products from Fakestore API.

Data Fetching

To fetch data with the useSWR, we need to create a fetcher function, and this function is just a wrapper of native javascript fetch or lib like axios. You can use the native fetch or even use GraphQL API too, learn how. The fetcher function also transforms our received data into JSON. So inside Products.js, paste the following code snippets:

//Products.js
import React from "react";
import axios from "axios";
import useSWR from "swr";
function Products() {
  const address = `https://fakestoreapi.com/products?limit=8`;
  const fetcher = async (url) => await axios.get(url).then((res) => res.data);
const { data, error } = useSWR(address, fetcher);

if (error)
    return (
      <div>
        <h1>404</h1>
        <p>Loading failed...</p>
      </div>
    );
  if (!data)
    return (
      <div>
        <h1>Loading...</h1>
      </div>
    );
  return (
    <div>
      <div className="container">
        {data &&
          data.map((item) => (
            <div key={item.id} className={`user-card  ${item.gender}`}>
              <div className="basic">
                <img
                  src={item.image}
                  alt="item-avatar"
                  className="item-avatar"
                />
              </div>
              <div className="details">
                <h3> ${item.price}</h3>
                <p>{item.title}</p>
                <button
                  onClick={() => alert("cart functionalities available soon!")}
                >
                  Add To Cart
                </button>
              </div>
            </div>
          ))}
      </div>
    </div>
  );
}
export default Products;
Enter fullscreen mode Exit fullscreen mode

In the above snippets, we imported useSWR from “swr” and also axios from “axios”, saving the FakeStore API to a variable called address. There’re three states of the useSWR request, loading state(request ongoing), data state( request successful), and error state(failed request).

So we pulled out “data” and “error” from useSWR and returned the corresponding UI. For the data state, then used conditional rendering to check if it’s successful and if it is, loop through the data and display each item details to the UI, import the Products component inside the App.js and render it. In the browser, it should look like so:

data-fetched

Error Handling

The useSWR hook does a great job handling errors. For instance, if there’s an error in the “fetcher” function, the hook returns it as an error.

    const { data, error } = useSWR(address, fetcher);
Enter fullscreen mode Exit fullscreen mode

So the error object becomes defined, and thus the promised rejected, and our error corresponding UI is displayed. In some cases, we would want to work with the error object and the status code; we can customise the “fetcher” function to give us the error status.

Auto Revalidation

The SWR gives us the option to automatically refetch data using the revalidateOnFocus, refreshInterval, revalidateIfStale, revalidateOnReconnect and some other methods you can find in the docs. Some are enabled on default like the revalidateOnFocus and revalidateOnReconnect; others are not.

These methods can be convenient when your applications involve data with high-level-frequency updating.

If for a reason these methods or one of them does not suite your demand you can turn them off like below:

    const { data, error } = useSWR(address, fetcher, {
          revalidateIfStale: false,
          revalidateOnFocus: false,
          revalidateOnReconnect: false,
        });
Enter fullscreen mode Exit fullscreen mode

Mutation

Sometimes we might want to locally revalidate our data and make the feel faster instead of waiting for the automatic revalidation options. SWR uses the mutate() to make that possible.

There are two ways to get the mutate function.

  • Pulling it out of the useSWR hook like below:
    const { data, error, mutate } = useSWR(address, fetcher);
Enter fullscreen mode Exit fullscreen mode

The mutate pulled from useSWR is pre-bound to that particular request, i.e. it can be used anywhere within a component without having to pass the request key to it. We can then go-ahead to call the mutate() in an onClick or any other event or function you want.

  • Getting it from SWR function useSWRConfig().

In some cases, we might want to revalidate data in different components or pages. We’ll then get the mutate from useSWRConfig() and pass a request key to it.

The request key is mostly the URL we passed to the fetcher function. Below is an example component that would not reflect in our project, just a demo code block that shows how to use mutate() from useSWRConfig.

     import { useSWRConfig } from 'swr'

    export function DeleteItem () {
      const { mutate } = useSWRConfig()

      return (
        <div>

          <button onClick={(item-id) => {
          //some codes to delete the said item

            mutate(`${address}`)
          // tell SWRs request(s) with this key to revalidate
          }}>
            Delete
          </button>
        </div>
      )
    }
Enter fullscreen mode Exit fullscreen mode

Once we “click” the delete button in the above code, we will be deleting a particular item; afterwards, we call the mutate() to revalidate every request with the address we passed to the mutate function.

For instance, if we are getting the products from a local /products endpoint, upon deleting a product in the backend, the frontend needs a communication of what changed in other to remain in sync and not serve stale data to the UI.

The SWR uses the mutate() to make the communication, thus checking if there is a change, and if there is, revalidate and render the new change without reloading the entire page.

Pagination

In some cases, we might want to load more items either in an onClick or onScroll events. SWR goes as far as requesting the next sets of data even before we click the “load more” or “next” button and just render the data when the corresponding event is triggered.

Let’s see how it works; update “App.js” to look like below:

    import "./App.css";
    import Header from "./Components/Header";
    import Products from "./Components/Products";
    import { useState } from "react";
    function App() {
      const [count, setCount] = useState(8);
      return (
        <div className="App">
          <Header />
          <Products count={count} setCount={setCount} />
        </div>
      );
    }
    export default App;
Enter fullscreen mode Exit fullscreen mode

Now let’s pull out the “count” and “setCount” and use them in the Products.js component.

//Products.js
    import React from "react";
    import axios from "axios";
    import useSWR from "swr";
    function Products({ count, setCount }) {
      const address = `https://fakestoreapi.com/products?limit=${count}`;
      const fetcher = async (url) => await axios.get(url).then((res) => res.data);
      const { data, error, mutate } = useSWR(address, fetcher, {
        revalidateIfStale: false,
        revalidateOnFocus: false,
        revalidateOnReconnect: false,
      });

    //check error and display corresponding UI
      if (error)
        return (
          <div>
            <h1>404</h1>
            <p>Loading failed...</p>
          </div>
        );
    //check loading and display corresponding UI
      if (!data)
        return (
          <div>
            <h1>Loading...</h1>
          </div>
        );
    //create loadMore function
      const LoadMore = () => {
        setCount(count + 4);
        mutate();
      };
      return (
        <div>
          <div className="container">

          ///check and display Items

          </div>
          <div className="btns">
            <button onClick={() => LoadMore()}>Load More</button>
          </div>
        </div>
      );
    }
    export default Products;
Enter fullscreen mode Exit fullscreen mode

In the browser, when we click on “Load More…”, the UI for the data loading state(Loading…) is displayed, followed by the data state and UI gets updated(more items fetched).

pagination

Is that just it? Not yet, there’s a better experience. Because of SWR's cache, we can preload the next set of items or a next page and render them inside a hidden div. SWR will trigger the data fetching of the next items even before the “Load More…” button is clicked. When the user then clicks the “Load More…” button or navigates to a next page, the data is already there, lets's update App.js and render the hidden div.

import "./App.css";
  import Header from "./Components/Header";
    import Products from "./Components/Products";
    import { useState } from "react";
      function App() {
      const [count, setCount] = useState(8);
      return (
        <div className="App">
          <Header />
          <div style={{ display: "none" }}>
            <Products count={count + 4} />
          </div>
       <Products count={count} setCount={setCount} />
       </div>
      );
    }
    export default App;
Enter fullscreen mode Exit fullscreen mode

pagination1

You see! the loading UI is gone, and our items are fetched.

The SWR has other exciting features that we’ll not cover in this post like

  • Conditional and Dependent Data Fetching.
  • SSR and SSG support
  • TypeScript support

Read about them in the docs, if you are interested.

SWR using its built-in cache and deduplication, skips unnecessary network requests or re-renders, plus it ships no unnecessary codes.

What this means is that if you are only importing the core useSWR API, unused APIs like useSWRInfinite or useSWRConfig won't be bundled in your application.

Conclusion

The SWR library is so amazing not to keep an eye on; we have seen some of its highlighted features in practice, and we hope you are convinced about its awesomeness.

The hook is backend-agnostic, which means it can fetch data from any remote API or database; it doesn't matter what software or the language your backend is running.

Top comments (0)