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:

  1. SWR starts to fetch initial data, app shows loading state.
  2. SWR has fetched data, app shows data.
  3. SWR starts to fetch data again (revalidation), app still shows data.
  4. 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!