Getting Started With useSWR For Client Side Data Fetching in React and Next.js

Getting Started With useSWR For Client Side Data Fetching in React and Next.js

Saves You the Stress of Writing Your Own Logic

Hello folks! This article will be brief and aimed at onboarding you to the useSWR hook for fetching API data. The name SWR is derived from the concept called "stale-while-revalidate", which is an HTTP cache invalidation strategy. The hook comes with a display of benefits which includes:

  • Simplicity of logic
  • Lightweight
  • Offers revalidation
  • Backend agnostic
  • Easy pagination
  • Supports TypeScript and a host of other perks.

For this onboarding article, we will be demonstrating the practical use of the SWR hook by utilizing the GitHub REST API endpoint to return some of our account data and render them on a simple profile user interface. Sounds straight to the point, right?

Pre-requisites

This article assumes you are familiar with JavaScript, React or Next.js technologies. In addition, a good knowledge of hooks and asynchronous JavaScript would be vital. Let's dive straight in!

How SWR Works

The useSWR hook takes in two parameters called key and fetcher. The key is something which uniquely identifies the data to be fetched, usually, the API URL, while the fetcher is a function (can be asynchronous), which returns the data. Awesome! Now the hook itself returns two values, data and error, representing the request's status; these two values can be used to set a loading state and an error fallback respectively.

The significant perk the SWR hook offers is how it simplifies the data-fetching logic. With just two lines of code, we can get our API data delivered (or error response returned). Furthermore, for each request made, a cached version is first returned, while the server revalidates the request and returns updated data immediately after revalidation. This singular feature helps optimize your web app, improving speed and reducing loading time. Sounds cool, doesn't it?

Setting Up Simple Profile Page

For the scope of this article, we will be using the React framework called Next.js to demonstrate the useSWR hook by setting up a simple profile page and fetching data from the GitHub API.

Firstly, we will set up a Next.js application using the command below:

yarn create next-app
# or
pnpm create next-app

Secondly, we have to install the useSWR hook and the data fetching library called Axios.

yarn add swr 
#or
npm install swr

Install the Axios library by using the command below:

yarn add axios
#or
npm install axios

Do not forget to start your Next.js server and let's start fetching our data from the GitHub API. Right on the index.js page, let us simply clean up the templated component and start utilizing the hook.

useSWR with Axios

Using the popular Axios library, we simply need to define a fetcher function asynchronously, this means the function returns a promise containing the data, otherwise an error object. I am using my own GitHub username to fetch my profile data from the GitHub REST API, feel free to replace it with your own or any other username.

NB: If you must make sensitive requests such as getting your private repos, creating or deleting a repo, you will need an authentication access token. Check out how you can generate your own access token.

  const address = `https://api.github.com/users/EOEboh`;

  const fetcher = async (url) => await axios.get(url);

Afterwards, we simply need to pass the base URL and our fetcher function to the useSWR hook as illustrated below.

const { data, error } = useSWR(address, fetcher);

  if (error) <p>Loading failed...</p>;
  if (!data) <h1>Loading...</h1>;

 console.log('success message', data);

And that is all it takes to fetch our data from the API, easy right? All we need to do is to render them on our user interface.

Feel free to check out the live CodeSandBox demo here.

Why Use SWR?

  • Easy Maintainability and Flexibility

    Traditionally, we would fetch data from an API and wrap the fetch function in the useEffect hook, this data can then be rendered on the user interface the way we want. However, when this data is needed by child components, we have to pass them through props as illustrated below:
// main component

function MainComponent () {
 const [data, setData] = useState([]);
  const getData = async () => {
    const { data } = await axios.get(`http://api.github.com/`);
    setData(data);
  };

  useEffect(() => {
    getData();
  }, []);

  // global loading state
  if (!data) return '...Loading'

  return <div>
    <Navbar user={data} />
    <Content user={data} />
  </div>
}

// child components
function Navbar ({ data }) {
  return <div>
    ...
    <Avatar user={data} />
  </div>
}

function Content ({ data }) {
  return <h1>Welcome {data?.data?.name}</h1>
}

function Avatar ({ data }) {
  return <img src={data?.data?.avatar_url} alt={data?.data?.name} />
}

Looking at the above code, it becomes harder to maintain when we keep adding more and more data dependencies. In addition to that, the child components may become very dynamic, and this makes it harder for the main component to know which data to provide to each child component even when using Context API or Redux.

The above problem is easily solved using SWR by utilizing the useUser hook to abstract data to the level of each component, this means the data is bound to the component and makes each component independent of the other. The parent component does not need to know which data is to be passed around, it just renders the data on the user interface. A perfect use-case of Separation of Concerns (SoC).

// main component

function MainComponent () {
  return <div>
    <Navbar />
    <Content />
  </div>
}

// child components

function Navbar () {
  return <div>
    ...
    <Avatar />
  </div>
}

function Content () {
  const { data, isLoading } = useUser();

  if (isLoading) return '...Loading'
  return <h1>Welcome back, {user.name}</h1>
}

function Avatar () {
  const { data, isLoading } = useUser()
  if (isLoading) return '..Loading'
  return <img src={data?.data?.avatar_url} alt={data?.data?.name} />
}

Furthermore, the load speed is faster because the hook sends just one HTTP request to the API and the request is cached in a global store and automatically shared across all components!

  • Pagination

    SWR provides an API called useSWRInfinite for achieving complex pagination patterns. Interestingly, for common pagination patterns, you do not have to use the useSWRInfinite hook, rather the useSWR hook would be sufficient. Take the example below:
function Page ({ index }) {
  const { data } = useSWR(`/api/data?page=${index}`, fetcher);

  // ... handle loading and error states

  return data.map(item => <div key={item.id}>{item.name}</div>)
}

function Pagination () {
  const [pageIndex, setPageIndex] = useState(0);

  // The API URL includes the page index, which is a React state.
  const { data } = useSWR(`/api/data?page=${pageIndex}`, fetcher);

  // ... handle loading and error states

  return <div>
    {data.map(item => <div key={item.id}>{item.name}</div>)}
    <button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
    <button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
  </div>
}

Of course, this works if the API has a provision for pagination. Also, since SWR provides caching, the preceding and previous pages are pre-fetched or preloaded when rendered in the abstracted div element. Other types of pagination such as Infinite Loading can also be achieved easily using SWR.

Conclusion

The SWR hook saves us the stress of writing our own logic for data fetching, making our code a lot cleaner and simpler. A better option to using the useEffect hook and perfect for static generated sites. Furthermore, it offers revalidation, and even more advanced options such as custom caching, middlewares and TypeScript support. Feel free to check out the official documentation to learn more.