Performance
SWR provides critical functionality in all kinds of web apps, so performance is a top priority.
SWR’s built-in caching and deduplication skips
unnecessary network requests, but the performance of the useSWR
hook itself
still matters. In a complex app, there could be hundreds of useSWR
calls in a
single page render.
SWR ensures that your app has:
- no unnecessary requests
- no unnecessary re-renders
- no unnecessary code imported
without any code changes from you.
Deduplication
It’s very common to reuse SWR hooks in your app. For example, an app that renders the current user’s avatar 5 times:
function useUser() {
return useSWR('/api/user', fetcher)
}
function Avatar() {
const { data, error } = useUser()
if (error) return <Error />
if (!data) return <Spinner />
return <img src={data.avatar_url} />
}
function App() {
return (
<>
<Avatar />
<Avatar />
<Avatar />
<Avatar />
<Avatar />
</>
)
}
Each <Avatar>
component has a useSWR
hook inside. Since they have the same
SWR key and are rendered at the almost same time, only 1 network request will
be made.
You can reuse your data hooks (like useUser
in the example above) everywhere,
without worrying about performance or duplicated requests.
There is also a dedupingInterval
option for overriding the
default deduplication interval.
Deep Comparison
SWR deep compares data changes by default. If the data
value isn’t
changed, a re-render will not be triggered.
You can also customize the comparison function via the
compare
option if you want to change the behavior. For
example, some API responses return a server timestamp that you might want to
exclude from the data diff.
Dependency Collection
useSWR
returns 3 stateful values: data
, error
and isValidating
, each
one can be updated independently. For example, if we print those values within a
full data-fetching lifecycle, it will be something like this:
function App() {
const { data, error, isValidating } = useSWR('/api', fetcher)
console.log(data, error, isValidating)
return null
}
In the worst case (the first request failed, then the retry was successful), you will see 4 lines of logs:
// console.log(data, error, isValidating)
undefined undefined true // => start fetching
undefined Error false // => end fetching, got an error
undefined Error true // => start retrying
Data undefined false // => end retrying, get the data
The state changes make sense. But that also means our component rendered 4 times.
If we change our component to only use data
:
function App() {
const { data } = useSWR('/api', fetcher)
console.log(data)
return null
}
The magic happens — there are only 2 re-renders now:
// console.log(data)
undefined // => hydration / initial render
Data // => end retrying, get the data
The exact same process has happened internally, there was an error from the
first request, then we got the data from the retry. However, SWR only updates
the states that are used by the component, which is only data
now.
If you are not always using all these 3 states, you are already benefitting from this feature. At Vercel, this optimization results in ~60% fewer re-renders.
Tree Shaking
The SWR package is tree-shakeable
and side-effect free. That means if you are only importing the core useSWR
API, unused APIs like useSWRInfinite
won’t be bundled in your application.