16

I was asked a question regarding SWRs "loading" state:

How do you create a loading state from SWR between different URL fetches?

Their docs make it appear straight forward:

  const { data, error } = useSWR(`/api/user/${id}`, fetcher)
  const isLoading = !error && !data;

However, this logic seems to fail after the first render of the hook/component. On the first render data is undefined. Then loads and data becomes a value to consume in the UI.

Let's say I change the id via the UI and want to show loading indicator. Because data is no longer undefined, the same logic fails.

There is an additional item returned isValidating. So I updated my logic:

const isLoading = (!data && !error) || isValidating

However, this could be true when:

there's a request or revalidation loading.

So in theory something else causes my component to rerender. This could inadvertently cause a "revalidation" and trigger loading state gets shown. This could break the UI temporarily, by accident.

So how do you derive "loading" between URL changes without revalidation? I am trying to replicate how graphQL Apollo Client returns const { loading, error, data } = useQuery(GET_DOGS);

Phil Lucks
  • 2,704
  • 6
  • 31
  • 57

2 Answers2

14

Let's say I change the id via the UI and want to show loading indicator. Because data is no longer undefined, the same logic fails.

data will be undefined again when you change the key (id), if it doesn't have a cache value.

Remember that in SWR { data } = useSWR(key) is mentally equivalent to v = getCache(k), where fetcher (validation) just write to the cache and trigger a re-render.

data is default to undefined, and isValidating means if there's an ongoing request.

Shu Ding
  • 1,506
  • 13
  • 17
3

Alternatively, you can derive loading through the use of middleware. Here's what I use...

loadingMiddleware.ts

import { useState } from 'react'
import { Middleware } from 'swr'

const loadingMiddleware: Middleware = (useSWRNext) => (key, fetcher, config) => {
  const [loading, setLoading] = useState(false)

  const extendedFetcher = (...args) => {
    setLoading(true)
    try {
      return fetcher(...args)
    } finally {
      setLoading(false)
    }
  }

  const swr = useSWRNext(key, extendedFetcher, config)

  return { ...swr, loading }
}

export default loadingMiddleware

App.tsx

import { SWRConfig } from 'swr'
import loadingMiddleware from './loadingMiddleware'

const App: FC = () => {
  ...

  return (
    <SWRConfig value={{ use: [loadingMiddleware] }}>
      ...
    </SWRConfig>
  )
}

export default App

Update (12/13/22)

swr@v2 is out and provides isLoading and isValidating properties in the return value of useSWR.

Here's the difference between the two according to the swr docs.

  • isValidating becomes true whenever there is an ongoing request whether the data is loaded or not.
  • isLoading becomes true when there is an ongoing request and data is not loaded yet.
bflemi3
  • 6,698
  • 20
  • 88
  • 155
  • By "loaded," do they mean that there is a cached version of the data? – OGreeni Jan 10 '23 at 17:42
  • 1
    Yes. In other words, the `data` property returned by `useSWR` is hydrated. They also state "Fallback data and previous data are not considered "loaded data," so when you use fallback data or enable the keepPreviousData option, you might have data to display." – bflemi3 Jan 11 '23 at 18:23