swr icon indicating copy to clipboard operation
swr copied to clipboard

`fallback` prop does not work for useSWRInfinite

Open davidfurlong opened this issue 2 years ago • 4 comments

Bug report

Description / Observed Behavior

What kind of issues did you encounter with SWR?

useSWRInfinite docs say you can use all of the useSWR options, however the fallback prop does not populate data

Expected Behavior

How did you expect SWR to behave here?

fallback should populate data

Repro Steps / Code Example

Or share your code snippet or a CodeSandbox link is also appreciated!

https://codesandbox.io/s/lingering-mountain-nxxmbn?file=/pages/index.js

Additional Context

SWR version.

see sandbox. swr 1.3.0 Add any other context about the problem here.

davidfurlong avatar Nov 09 '22 16:11 davidfurlong

@davidfurlong The key of useSWRInfinite is a special format, so you have to use unstable_serialize to get the key.

import useSWRInfinite, { unstable_serialize } from "swr/infinite";

export async function getServerSideProps() {
  const repoInfo = await fetcher(API);
  return {
    props: {
      fallback: {
        [unstable_serialize(() => API)]: repoInfo
      }
    }
  };
}

koba04 avatar Nov 10 '22 10:11 koba04

Using the latest RC 2.0.0-rc.0, the provided examples are incorrect for swr/infinite since unstable_serialize does not produce the same key used by useSWRInfinite.

Instead, the infinite version has two changes:

  • prepends the key with $inf$
  • expects the data to be an array of API responses instead of an API response

As an example, here's a normal key created with unstable_serialize:

`#url:"/api/search",key:"genre-crime",body:#query:"",orderBy:"relevancyScore",layout:"list",genres:@"crime",,foreign:false,,`

And the cache key used by the exact same request with useSWRInfinite:

`$inf$#url:"/api/search",key:"genre-crime",body:#query:"",orderBy:"relevancyScore",layout:"list",genres:@"crime",,foreign:false,,`

@koba04's example would need to become:

import useSWRInfinite, { unstable_serialize } from "swr/infinite";

export async function getServerSideProps() {
  const repoInfo = await fetcher(API);
  return {
    props: {
      fallback: {
        [`$inf$${unstable_serialize(API)}`]: [ repoInfo ]
      }
    }
  };
}

@shuding @koba04 the docs need to be updated to reflect this and/or introduce a different fallback prop for the infinite use case since it's key generation and result type are both different. Ideally, these requests would use the same key + value schema for both useSWR and useSWRInfinite so consumers of this library don't have to handle them differently, but that's not how things currently work.

Note: if you want to test this, I've found the following two strategies helpful:

// add an extra long delay in your API route to accentuate any client-side swr cache misses
await new Promise((resolve) => {
  setTimeout(resolve, 5000)
})
// log the contents of the cache in your client-side component using `useSWR` or `useSWRInfinite`
const { cache } = useSWRConfig()
console.log(
  'cache',
  Object.fromEntries(Array.from(cache.keys()).map((k) => [k, cache.get(k)]))
)

This is likely the cause of many issues such as #1520, #2164, #1885, and #1895.

transitive-bullshit avatar Nov 16 '22 22:11 transitive-bullshit

@transitive-bullshit Did you import unstable_serialize from swr/infinite not swr? I think this should add the prefix for useSWRInfinite.

https://github.com/vercel/swr/blob/e78bb59f38c2bb66ed2260ce986c93612ca44d51/infinite/index.ts#L40-L42

koba04 avatar Nov 30 '22 10:11 koba04

My findings around this issue are that there are two cache entries that are not set or, at least, properly set when providing a fallback. Our use case is mostly concerned around passing 1st page fallback.

This is a link to a working project with 2 pages using a workaround: https://codesandbox.io/p/sandbox/github/tomascasas/swr-infinite-fallback-and-cache/tree/main

If you run the app and navigate to / will show behavior when no 1st page fallback is passed.

When you navigate to /minimal will show behavior when 1st page fallback is passed.

The workaround mostly cares about setting the proper entries in cache, that otherwise revalidateFirstPage and revalidateIfState will ignore.

  useEffect(() => {
    if (firstPageData) {
      boundMutate([firstPageData], {
        revalidate: false,
        populateCache: true,
      });
      globalMutate(getFirstPageKey(getKey), firstPageData, {
        revalidate: false,
        populateCache: true,
      });
    }
  }, []);

With this block of code living next to useSWRInfinite and using boundMutate the one coming from the hook and globalMutate, the one coming from useSWRConfig, those cache entries are in place.

That makes revalidateFirstPage: false + revalidateIfStale: false + revalidateOnMount: undefined work as expected.

I am not yet familiar enough with implementation to be able to collaborate baking a fix, but would love to help in any possible way to solve, what I think, is a bug in how the fallback fails to land on cache.

tomascasas avatar Oct 07 '23 21:10 tomascasas