DEV Community

Cover image for How to Use SWR for Better Data Fetching Approach
Furkan Emin Can
Furkan Emin Can

Posted on • Edited on • Originally published at femincan.dev

How to Use SWR for Better Data Fetching Approach

In React, external data is very important to make your project more dynamic. However, when you use useEffect for data fetching, it is generally hard to manage the state, fortunately SWR simplifies this process.

Introduction to SWR

The first thing I want to say is what SWR means.

According to its documentation:

The name “SWR” is derived from stale-while-revalidate, a HTTP cache invalidation strategy popularized by HTTP RFC 5861. SWR is a strategy to first return the data from cache (stale), then send the fetch request (revalidate), and finally come with the up-to-date data.

So, SWR is a library that provides React hooks for data fetching.

Implementing SWR in Your Project: My Experience

I recently completed the first project for the Front End Libraries Certification of FreeCodeCamp. I tried SWR in this project and will share my experience.

Note: For more information, you can check the project's GitHub repository and CodeSandbox.

I used TypeScript for this project, but I will give you examples with JavaScript. So, don't worry if you don't know TypeScript. (I highly recommend it if you don't know.)

1. Creating fetcher function

I was planning to use Axios for the requests, so I created the async function below.

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

  return data;
};
Enter fullscreen mode Exit fullscreen mode

This function is just a wrapper for the get method of Axios. It accepts a url parameter and returns data.

2. Creating a hook that fetches a random quote

Based on the section Make It Reusable in SWR documentation, I created a hook called useRandomQuote.

Note: In this project, I used Quotable API for random quotes.

This hook is a wrapper for useSWR hook.

In my case, the API returns an array with one element which is the random quote. Since data is undefined during the request, I used optional chaining.

And, the hook returns an object that contains the quote variable with the variables destructured from useSWR hook.

Let's check the code and inspect it:

import useSWR from 'swr/immutable';

const useRandomQuote = () => {
  const { data, ...restSWR } = useSWR(
    'https://api.quotable.io/quotes/random',
    fetcher
  );

  return {
    ...restSWR,
    quote: data?.[0],
  };
};

export { useRandomQuote };
Enter fullscreen mode Exit fullscreen mode

In your projects, you almost always use useSWR hook.

Take a closer look at the useSWR hook:

const { data, error, isValidating, isLoading, mutate} = useSWR(
  key,
  fetcher
);
Enter fullscreen mode Exit fullscreen mode
  • Firstly we imported useSWR hook.

  • Then we used it in our custom hook with two parameters: key and fetcher.

    key is a unique string for the request which is like an id.

    fetcher is an async function that accepts key and returns data.

  • Finally, it returns data, error, isValidating, isLoading, and mutate variables.

    data is the variable returned from fetcher. During the first request, it will be undefined.

    error is the error thrown by fetcher. If there is no error, it will be undefined.

    isValidating is a boolean that indicates the request's status. It is true during each request including the first request.

    isLoading also is a boolean that indicates the first request's status. It is true during the first request, then it will always be false.

    mutate is a function that is used to modify the data manually.

If you noticed, I imported useSWR hook from swr/immutable in the first code snippet. It simply disables SWR's auto-revalidation. I prefer to use it because I want to revalidate the data manually with mutate function.

Understanding Auto-Revalidation

If the data you used is stale, SWR revalidates the data (re-fetches from the server) to keep freshness.

By default, SWR automatically revalidates the data (It assumes that the data is stale) in three cases:

  1. Every time the component is mounted, even if there is data in the cache, it revalidates.

  2. It revalidates when the window gets focused.

  3. It revalidates when the browser regains its network connection.

3. Mutating the Data

If you want manually revalidate the data, you can use mutate function.

I have QuoteCard component that displays the current quote. This component has a New Quote button that handles the mutation of the quote.

const QuoteCard = () => {
  const { quote, isValidating, mutate } = useRandomQuote();

  return (
    {/* ... */}
    <button
      // ...
      onClick={() => mutate()}
    >
      New Quote
    </button>
    {/* ... */}
  )
}
Enter fullscreen mode Exit fullscreen mode

When the mutate function is called, SWR revalidates the data. During this process, the quote variable remains the same (will not be undefined), and the isValidating variable becomes true.

Here is a diagram of this process:

A Diagram illustrating the fetch and revalidate pattern in SWR.

4. Handling Concurrent Requests

If you don't handle request cancellation manually, the ongoing request continues even if the user initiates a new request alongside it, resulting in multiple concurrent requests.

SWR only updates the data after the last request is completed. If there are concurrent requests, their responses aren't important except for the last one. So if you don't cancel these requests, it results in unnecessary network usage.

I faced the same issue in my project. When the user clicks the New Quote button while a request is still loading, the application triggers a new request alongside the existing one.

Visualization of concurrent requests being triggered by clicking the New Quote button continuously.

Although I checked SWR's documentation, I couldn't find a built-in solution for this issue. (I also checked React Query, and it has a built-in solution for that. I am planning to experiment with it in my next project involving API usage.)

In this scenario, I could use AbortController to handle request cancellation, but I didn't use it to keep things simple. I just disabled the New Quote button during validation.

Conclusion

SWR helped me a lot in this project. If you have not used it previously, I highly recommend trying it in your future projects.

This is my first blog post, and I will be waiting for your feedback and thoughts about the library in the comments.


Stay in Touch

Resources

Top comments (9)

Collapse
 
receter profile image
Andreas Riedmüller

Thanks, I did not know about swr/immutable, good to know that this exists.

Regarding the cancellation, did you test what SWR does when you call mutate while the request is still loading? I think it updates with the data from the first call when it is returned first and then gets overridden by the second response of the secong mutate/request right?

Cancelling would also come with issues, if the API response is slower then the frequency of calling mutate it would never show a result.

The only real issue would be if the first mutate gets a response later (after the response originating from the second mutate call) and overrides the data of the second response which was returned faster. But I am quite confident this is handle correctly by SWR. Didn’t find information about this behavior in the docs though.

I did recently use SWR in a Chrome Extension for accessing local storage, also very handy.

You can spread the remaining properties of useSWR in your hook you might be more future proof and it is a little less code, like so:

import useSWR from 'swr/immutable';

const useRandomQuote = () => {
  const { data, ...restSWR } = useSWR(
    'https://api.quotable.io/quotes/random',
    fetcher
  );

  return { ...restSWR, quote: data?.[0] };
};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
femincan profile image
Furkan Emin Can • Edited

First of all, thank you so much for taking the time to leave such a detailed and thoughtful comment on my first article.

I'd like to start with your first two questions:

As you mentioned your second question when you call the mutate function before the existing request is completed, the existing request continues while the new request starts concurrently. But, SWR only uses the last request's response to replace the current data. As a result, other requests become unnecessary.

Concurrent Request Example

If you want to check the code in the gif above, I prepared a branch in CodeSandBox: codesandbox.io/p/github/femincan/q...

I apologize, but I couldn't fully grasp the points you made in the canceling section of your comment.

Lastly, thank you for your suggestion. I have updated my code and article based on it.

Collapse
 
receter profile image
Andreas Riedmüller • Edited

Ah ok, so it looks like it is ignoring the old request responses, kind of makes sense.

What I meant with the frequency: If you never would have stopped clicking the "New Quote" button the Quote would never have updated. Even though newer data would have arrived already. I hope this is more understandable.

The good thing about this behavior: it does not look like it is randomly updating the quote after mutate is called multiple times.

This would be not ideal if the API responses are very slow and mutate/update would happen very frequently. Which to be honest is not a very realistic use case.

In this case I would not worry about cancelling the requests, it just makes things more complicated and has no big advantage.

If you want to cut down on requests eg. when typing in a TextBox or to let people hammer that button, you could also use throttle or debounce for the api calls.

For the random quote, disabling the button while fetching is a good solution.

And one thing for your code that just came to my mind: If the code for extracting the quote from data is moved to inside the fetcher logic you have all API related code in one place.

And I would rather use { ...restSWR, quote } than { quote, ...restSWR } because the later always wins over the previous. And as ´quote´ is your own property you would never want that to be overriden by restSWR. (This would only happen if useSWR adds a property called quote though.)

Thread Thread
 
femincan profile image
Furkan Emin Can • Edited

I just updated the article to make the section clearer.

I'm not agree with you that "In this case I would not worry about cancelling the requests, it just makes things more complicated and has no big advantage."

If the user's network connection is slow, It results in a long loading process. If we cancel the unnecessary requests, the last request completes faster. You can check the updated section for more information.

Thanks for your suggestions.
I think, I can move the fetcher function inside the useRandomQuote hook. It seems more appropriate for me.
I will also update the return statement based on your suggestion.

Thread Thread
 
receter profile image
Andreas Riedmüller

You are right, it can make a difference, especially if the responses are quite big. This would be a nice feature for useSWR

I found that there is a RFC for cancellation with useSWR: github.com/vercel/swr/discussions/... and it has a pull request as well but not reviewed/merged yet.

Maybe it arrives soon.

Thread Thread
 
femincan profile image
Furkan Emin Can

I agree that it would be a nice addition to useSWR. I hope the pull request gets reviewed and merged soon.

I appreciate our discussion and your contributions to the topic. If you have any further questions or thoughts, feel free to share them.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
femincan profile image
Furkan Emin Can

Thanks for the additional information.

Collapse
 
Sloan, the sloth mascot
Comment deleted