Aug 25, 2021

Supercharging Data Fetching in React with SWR

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.

Share this on:X