swr: Data is not updated with `initialData`
I’m on a Next.js app and here is my (simplified) code:
const IndexPage = ({ data: initialData }) => {
const [filters, setFilters] = useState(defaultFilters)
const onChange = () => {
...
setFilters(newFilters)
}
const query = getQuery(filters)
const { data } = useSWR(`/api/resorts?${query}`, fetcher, { initialData })
return (...)
}
Index.getInitialProps = async ctx => {
const query = getQuery(defaultFilters)
const data: Resort[] = await fetcher(`${getHost(ctx)}/api/resorts?${query}`)
return { data }
}
I have an initial set of filter (defaultFilters) on which I query and pass to useSWR with initialData on first render. When the user changes one filter, the key should change as it’s a new query, but useSWR still returns the old data, from the first call.
When I remove initialData from useSWR it works but that’s not what I want, I want to have SSR.
Am I doing something wrong?
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 28
- Comments: 57 (10 by maintainers)
I don’t feel this is the way hooks work in this case. Almost the same argument would be to say
useStatedefault value should be set toundefinedby developer once you do firstsetStateor you would get the unchanged state all the time.Even more
initialDatais explicitly used as example for SSR. In this case the data is provided once and should be considered irrelevant once the key changes. Anytime I change the key I want to fetch new data and I don’t care about initialData anymore.If this is not a bug then maybe at least a suggestion for improvement in docs? Additionally some other prop could be added to api alongside
initialDataWhether this is a bug or intended, I think it should be documented properly (and same goes for the currently nowhere mentioned
useSWRPages).I think the real problem here is “fallback” vs “cache”.
initialDatawas designed to be per hook and it was considered as “fallback”. Let’s say 2 components:It will be very confusing if
initialData(the fallback) is bound to the key, if the keys are the same / are changing.Ideally, a better API design should be context (note this API doesn’t exist):
And all the SWR hooks inside the tree should use it as the data source.
Thanks for the suggestion! I applied the “undefined” fix.
Indeed it seems to be a bug because it’s not intuitive and not documented either…
For googlers who are puzzled when they have set initialData hoping for SWR to push fresh data when necessary (while also possibly fetching initialData in getServerSideProps), I found that setting revalidateOnMount: true (as suggested by @Romstar ) solved the issue of updating a page with e.g. a new item. It’s worth a try if that’s your issue, before going on to some of the more complex solutions suggested by other people here (at least it solved my update issue on page navigation).
Here’s a fully Typescript version of what @isaac-scarrott wrote for a workaround:
I also agree with @nfantone that this needs addressing as it seems like it’s going to continue causing significant issues going forward. I think we’re needing agreement from the maintainers on what the expected behaviour so we can start working on a fix.
I was against this at first, but the more I think about it the more I realise it goes against what I fell in love with about useSWR: it’s incredibly simple but insanely flexible.
I solve my use-case like this:
initialKeyis my initial algolia query formatted to a string that is passed to a simplefetch()which getsinitialDataingetServerSideProps.keyis the updated version of the algolia query (whenever a user searches or changes a filter) that signals to useSWR that its time to take over. Wheneverkeydoesn’t equalinitialKeywe knowinitialDatais no longer relevant.Sure, the initialData option is not intuitive for the SSR use-case, but adding a new option for every unintuitive case (that can be solved in user-land) that we discover will slowly morph this package into the the very thing we are escaping (atleast, what I am escaping anyway 😄)
I replicated it here https://codesandbox.io/s/eager-lovelace-hgj9h
A fix right now is to grab mutate from useSWR results and call it immediately. In the CodeSandbox there is a fix running useEffect to call the mutate function and revalidate automatically.
Another fix, also in the CodeSandbox, is to change initialData to undefined if the dynamic part (in your case the query) is not the same as the initial one, to do this in your case you will need to pass the “initialQuery” (the one from getInitialProps) to the component as a prop and use it to detect if you should use or no initialData.
I think this makes sense, SWR is using the initialData if there is no a cached data already as the value of your current key, when you change the key is using the same initialData for the new key.
I think the bug here, if it’s a bug and not intentional, is that SWR should detect the key changed and ignore the initialData, only use the cache.
I’ve made a custom hook/wrapper around useSwr that fixes this issue with Next.js if anyone is interested:
Whether or not it’s a bug, it’s clearly unexpected for quite a few people, and prevents one of the more common use-cases of server side rendering of dynamic routes without an unintuitive workaround. @shuding’s proposal looks to be a good one (and maybe we could rename the argument to SWRConfig “cache” for clarity?), so hopefully something like that lands soon.
(revalidating the initial data by default would lead to a second unnecessary request on first load for server side rendered pages - I believe that’s what the behaviour used to be and was changed, for this reason.)
I agree and I don’t see any good use case for current behaviour. If for each key there is always initial data updated on the server side then why do I need using this hook at all?
I also ran into this problem and used the workaround from @danielrbradley which worked great. It uses some deprecated types though which I had to update. My version ended up looking like this:
We released SWR 1.0 with better preloaded cache solution. Checkout cache doc for details.
For fallback purpose,
initialDatais renamed tofallbackDataif you don’t need any preloaded state in cache.I can’t believe this bit me again today 😥. A customer raised an issue explaining that they “weren’t seeing the new email entry in their table after creating it”. And, lo and behold, it was from an older project where the workaround proposed here for
initialDatawas not in place.At this point, I believe this issue has been open for long enough and really needs to be addressed. I’m open to putting together myself a PR to integrate the
didMountsolution intoswr, if there’s an interest for it.Let’s hear from collaborators.
OK so if I understand correctly currently the logic is that if I provide
initialDatafor a hook then both for initial key and future keys per this hook it’s considered a fallback if current key’s data is not in cache.In my understanding the key is often identifying data itself like for example
user/profile/1234orproducts/jeans. If I change the key to other user profile or product category I really do not expect the initialData try to provide defaults for me anymore.I think this does make sense and is not confusing right? It’s irrelevant what keys other hooks are using.
I ended up using this config option: revalidateOnMount: true.
Example:
const { data: listOfUsers, mutate: setListOfUsers } = useSWR(API_ROUTES.ADMIN.USERS, { fetcher, initialData: [], revalidateOnMount: true });and now my data is initialized as an empty array and the data is pulled after the first render which then causes the state to be updated and my array is populated with real data which then allows my app to work properly!
Now with
swr@beta, If you’re using next.js, you can use custom cache API to initialize the very first stateFell into the same rabbit hole as everybody else here.
I’m implementing a basic search results page where the first set of results is fetched server side (for SEO purposes). The way
useSWRcurrently works withinitialDatameans that while the very first list of results is returned just fine, new searches keep returning the same items. For some reason, I found that it won’t even trigger network requests even if the key changes, in some cases.@isaac-scarrott proposed solution actually worked for me 👍.
The only downside I see is that, unless using TS, you lose typings and intellisense on VSCode.
@lkbr
Right - but then you’d always be returning
initialDatafor the same original key, no matter when you do it, which might have become stale at some point.@Romstar As pointed out by @majelbstoat some comments ago, if you go down that route you’ll be triggering a second request for basically the same data - preventing that is one of the upsides of using SSR in the first place.
@shuding
IMHO, I don’t feel like this would be a smart way to go about this moving forward. Not only it would break logic and data locality significantly, but as the current Next.js API stands, it would also completely void the ability to statically optimize pages in your app that could benefit from it, right?
@pke I used useRef as this will not trigger a rerender where as useState will trigger a rerender. In this case we don’t want to UI to update when we change the value of hasMounted to true, so by using useRef instead of useState when we update the ref value this will not trigger a rerender and created a very small performance increase.
There is the another workaround using
cache. It allows to delete a cache entry synchronously before initialization ofuseSWRhook, so it removes the useless rerender from other solutions:However, it doesn’t delete other SWR cache entries for
isValidationanderrorstates. If you need that, there iscache.serializeKeymethod that returns additional keys.@jacobedawson thank you for the tip!
revalidateOnMount: truedoes revalidate.My issue now is that I’m fetching the same data twice in a row on the client for every client-side page route, once in getInitialProps (which is thrown out) and once again right after that when swr revalidates on mount. I’m considering detecting if the page is being rendered SSR and only fetching if the render is on the server.
@majelbstoat yep.