swr
swr copied to clipboard
`fallback` prop does not work for useSWRInfinite
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
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
}
}
};
}
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
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
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.