SWR (v1): show previous/stale data on revalidation errors
Published on in JavaScript and React
Last updated on
If I'm already displaying data that I have successfully fetched with SWR, and then another fetch call fails (when revalidating the data), I don't want to replace the data view with an error view. Here's how to avoid that.
Table of contents
Problem
Basic usage of SWR (v1; I haven't upgraded to v2 yet):
export const useItems = () => {
const { data, error } = useSWR('/api/items/', (url) =>
fetch(url).then((res) => res.json())
)
return {
items: data ?? [],
isLoadingItems: !data && !error,
failedToLoadItems: !!error,
}
}
function Items() {
const { items, isLoadingItems, failedToLoadItems } = useItems()
return isLoadingItems ? (
<p>Loading...</p>
) : failedToLoadItems ? (
<p>Error</p>
) : items.length === 0 ? (
<p>No items</p>
) : (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
}
My problem occurs with these steps:
- SWR starts to fetch initial data, app shows loading state.
- SWR has fetched data, app shows data.
- SWR starts to fetch data again (revalidation), app still shows data.
- SWR fails to fetch data (e.g. the API endpoint fails), app shows error state.
- Here I don't want the app to show error state; I want it to keep showing the previous (stale) data.
Solution
I spent some time looking for a solution
until I realized that it's very simple –
let the failed state be false
only if there's no data (i.e. if data
is not falsy):
export const useItems = () => {
const { data, error } = useSWR('/api/items/', (url) =>
fetch(url).then((res) => res.json())
)
return {
items: data ?? [],
isLoadingItems: !data && !error,
failedToLoadItems: !data && !!error,
// ^^^^^^^^
}
}
function Items() {
// Same as before
}
Truth tables
I built these truth tables to be sure that the change works as expected.
Before the change
data |
error |
isLoading ( !data && !error ) |
failedToLoad ( !!error ) |
state description |
---|---|---|---|---|
falsy | falsy | true |
false |
initial state or revalidation succeeded |
truthy | falsy | false |
false |
initial fetch succeeded |
falsy | truthy | false |
true |
initial fetch failed |
truthy | truthy | false |
true |
revalidation failed |
After the change
Only the 4th column and the last row are different:
data |
error |
isLoading ( !data && !error ) |
failedToLoad ( !data && !!error ) |
state description |
---|---|---|---|---|
falsy | falsy | true |
false |
initial state or revalidation succeeded |
truthy | falsy | false |
false |
initial fetch succeeded |
falsy | truthy | false |
true |
initial fetch failed |
truthy | truthy | false |
false (true in the other table) |
revalidation failed |
LGTM!