What is SWR?
Typically, when we call a REST API, we use fetch
or a third-party library like axios
, or, god forbid, $.ajax
and XHR
. The browser then makes an HTTP call and accepts a response. While the call is being made, the user waits, usually staring at a loading indicator.
SWR enhances data fetching with caching, revalidation, and request deduplication. The name "SWR" is derived from stale-while-revalidate
, an HTTP cache invalidation strategy. It is back-end agnostic and supports TypeScript. It exposes React hooks that we can use for data fetching in our applications.
Why use SWR instead of fetch/axios?
When we use SWR for data fetching, it returns the data from cache (stale), then sends the request (revalidate) and returns the up-to-date data. SWR will automatically cache the response the first time we fetch it.
This translates to a real-time experience for users who instantly see UI being rendered on-screen, while our application fetches the up-to-date data. SWR also has some cool features out-of-the-box:
- Auto revalidation
- Support for React Suspense
- Fast page navigation
- Pagination
- Scroll position recovery
- Retry on error
Why do we still need fetch/axios when using SWR
SWR does not replace native fetch
or axios
. We'll end up using them anyway because SWR only wraps around them. One of the arguments we pass to the useSWR
hook is a fetcher function that uses fetch
or axios
.
// Using Fetch API export const fetcher = async (url, opts = {}) => { const res = await fetch(url, opts) if (!res.ok) { throw new Error('Error completing request') } return await res.json() }
We can then use the useSWR
hook to fetch data.
const { data, error } = useSWR(`/api/users/${userId}`, fetcher)
We can also configure SWR globally in our applications. We can skip passing the fetcher function if we do this:
function App({ Component, pageProps }) { return ( <SWRConfig value={{ fetcher }}> <Component {...pageProps} /> </SWRConfig> ) }
The real power of using SWR shows when you can reuse data fetching code. When building an application, we might need to reuse the data we fetch in multiple places. SWR makes it incredibly easy to create custom data fetching hooks that we can use in any of our components.
export function useUser(userId) { const { data, error } = useSWR(`/api/users/${userId}`, fetcher) return { user: data?.user, isLoading: !error && !data, isError: error, } }
Now we can use the useUser
hook in our components like this:
function Profile({ userId }) { const { user, isLoading, isError } = useUser(userId) if (isLoading) return <div>Loading...</div> if (isError) return <div>An error occurred...</div> return ( <div> <img src={user.avatar} /> <span>{user.name}</span> <span>{user.email}</span> </div> ) }
Configuring revalidation
SWR offers multiple revalidation strategies. We can use one or all of them. We can also pick and choose which of our fetch requests use which strategies. We can also globally configure revalidation strategies in SWRConfig
provider.
- Revalidation on focus: When the tab comes into focus, SWR will revalidate. Enabled by default.
- Revalidation on network recovery: When our device reconnects to the internet, SWR will revalidate. Enabled by default.
- Polling on interval: It can automatically make fetch requests at specified intervals so our UI is always up-to-date. Disabled by default. If enabled, there are two more options that we can use to enable polling even when the browser tab is not active or when we're not connected to the internet.
Mutation
SWR will revalidate our data when one of the above strategies is used. We can also manually trigger revalidation using mutate(key)
. The key
is the URL where we fetch the data.
mutate(`/api/users/${userId}`)
The useSWR
hook also returns a mutate
function that we can use which doesn't need a key
.
const { user, mutate } = useSWR(`/api/users/${userId}`, fetcher)
POST requests
SWR lets us do optimistic rendering by local mutation — where we update the UI based on new data immediately — before we even update the database with POST requests.
const { user } = useUser(userId); ... const formSubmitHandler = async (values) => { // Update local data immediately and disable revalidation mutate(`/api/users/${userId}`, { ...user, ...values }, false); // Make the API request to update the database await updateUser(values); // Trigger revalidation to ensure local data is correct mutate(`/api/users/${userId}`); }
SWR is a better way to fetch data in our React applications. It abstracts away many features that we normally would have written ourselves if our application requires them. It also gives us good defaults so we don't have to spend a lot of time in configuration. Most important of all, it has a simple API and is easy to use.
There is also an alternative to SWR called react-query
which I haven't used, yet. I'll cover that in a future post.