apollo-client: AC3: useQuery does not return old data anymore when variable change
Intended outcome:
useQuery did return old data when variables changed. This was confusing, i admit, but was very useful.
see https://github.com/apollographql/apollo-client/issues/6039
Actual outcome:
useQuery has no longer this behavior. this was changed last minute and is not mentioned as a breaking change https://github.com/apollographql/apollo-client/pull/6566
because it is not mentioned, i consider it a bug. There should be a flag to restore this behaviour
How to reproduce the issue:
see https://github.com/apollographql/apollo-client/issues/6039 but reverse š
Versions
System:
OS: macOS 10.15.5
Binaries:
Node: 12.13.1 - ~/.nvm/versions/node/v12.13.1/bin/node
Yarn: 1.21.1 - ~/git/panter/veloplus/veloplus-shop/veloplus-shop-fe/node_modules/.bin/yarn
npm: 6.12.1 - ~/.nvm/versions/node/v12.13.1/bin/npm
Browsers:
Chrome: 83.0.4103.116
Firefox: 76.0.1
Safari: 13.1.1
npmPackages:
@apollo/client: 3.0.0 => 3.0.0
@apollo/link-persisted-queries: ^1.0.0-beta.0 => 1.0.0-beta.0
apollo: ^2.28.3 => 2.28.3
apollo-link-schema: ^1.2.5 => 1.2.5
apollo-upload-client: ^13.0.0 => 13.0.0
Edit: it is mentioned, but with a confusing wording.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 37
- Comments: 31 (4 by maintainers)
Commits related to this issue
- Provide a previousData property in useQuery/useLazyQuery results Alongside their returned `data` property, `useQuery` and `useLazyQuery` now also return a `previousData` property. Before a new `data`... — committed to apollographql/apollo-client by hwillson 4 years ago
- Provide a previousData property in useQuery/useLazyQuery results Alongside their returned `data` property, `useQuery` and `useLazyQuery` now also return a `previousData` property. Before a new `data`... — committed to apollographql/apollo-client by hwillson 4 years ago
- Provide a previousData property in useQuery/useLazyQuery results Alongside their returned `data` property, `useQuery` and `useLazyQuery` now also return a `previousData` property. Before a new `data`... — committed to apollographql/apollo-client by hwillson 4 years ago
- Provide a previousData property in useQuery/useLazyQuery results Alongside their returned `data` property, `useQuery` and `useLazyQuery` now also return a `previousData` property. Before a new `data`... — committed to apollographql/apollo-client by hwillson 4 years ago
- Provide result.previousData in useQuery/useLazyQuery results (#7082) In addition to the `result.data` property, `useQuery` and `useLazyQuery` will now provide a `result.previousData` property, whic... — committed to apollographql/apollo-client by hwillson 4 years ago
Iām happy to share that we finally have an implementation of
result.previousData
(#7082), and you can test it now using@apollo/client@3.3.0-beta.6
.We recommend the idiom
result.data ?? result.previousData
to obtain the most recent useful data (if youāre comfortable with it possibly being stale). While this may seem like more code than justresult.data
, the??
expression represents a choice that should be made consciously (specifically, that youāre okay with stale data), and explicitly enabled with a minimum of extra syntax. We hoperesult.previousData
gives you the power to make that choice, while ensuringresult.data
is never ambiguously stale.I cant find the rationale behind this change, is there anywhere a thread?
In our app we have filtering for example, and to simply rely on what is in cache and when its loading was great for us developers and our visitors. Now we are supposed to store the previous response on the query? That means a lot of useEffects which have to be added:( And why? Same about the loading state suddenly being true even when having its data aggregated on the server.
Personally i would like to read about these changes in the migration documentation, instead of only the changes involved. Because upgrading was easy, the new behaviour was surprisingly new.
That being said, im very happy the new version is out, congratulations
@benjamn works like a charm š, thanks a lot!
Some details I stumbled upon that might be interesting for others:
previousData
was always undefined (not sure why)for the cases where stale data is fine
I too was confused by this when upgrading, although the change does make sense as returning stale data isnāt always a good idea.
Instead of
previousData
, how about a simple boolean option such asreturnStaleData
(just likereturnPartialData
)? That way you can opt in to the old behaviour where it makes sense. The two places where I display stale data is on a search filtering page, and on a map, where it is useful to display the old results until the new results have finished loading. A loading spinner can be displayed on top of the old results by conditionally rendering onresult.loading
, so displaying stale data isnāt necessarily a bad design pattern.So I just found out about this change - not a huge fan š
Is the assumption that most projects are going to start storing (and updating) responses in their own local state rather than using the cache as the single source of truth? Sounds like a lot of new
useState
anduseEffects
are on their way!Is it possible to get this documented in big red letters in the hooks migration guide as I couldnāt seem to find any mention of this breaking change š
P.S. Thanks for all your hard work on Apollo š
Hello, we are having the same issue in multiple projects. Iām in favor of having a flag for
useQuery
where we can specify if we want to keep stale data. Thank you!i also came to that conclusion that i actually liked the old behaviour and was not aware that i relied so heavily on it!
having a flag per userQuery would also be my prefered solution as it is straight forward to migrate (as opposed to add (data ?? previousData) everywhere)
I hear you, but this was very much an intentional change, as I hope you can tell from the discussion that motivated it.
That said, the real problem with redelivering previous data was that it was delivered in the same way as current data (via
result.data
), so there was no way to tell if the data was fresh or stale. What do you think about providingresult.previousData
(or even a chain ofresult.previousResult.previousResult...
) to allow application code to explicitly choose to reuse old data, when convenient?You can use this snippet, there is really not a lot of code involved:
Below just use
cachedData.current
.While I donāt yet have on opinion on the quality of the change, I find it surprising that this isnāt mentioned in the Apollo v3 migration docs. @benjamn, is there a roadmap to updating the migration docs with all of the intentionally backwards incompatible changes, or a rationale for not including them otherwise?
Iām also not a fan of this change. Iāve used the previous behavior to my advantage pretty much everywhere. I always show a āfull pageā loading indicator on initial load (when data is undefined and loading is true), then, on subsequent loads (variable change etc.) I show a partial loading indicator (e.g. a small spinner in the corner or something).
With this change, all my previous work becomes a twitchy and jumpy nightmare to use due to the āfull pageā loading always showing up, and changing this everywhere would require a lot of work.
I think I understand the issue with not wanting to show a loading indicator if the data returned is actually from the cache (using fetch policy ācache-and-networkā), but this seems like such a niche use case to meā¦
In my opinion, instead of returning āpreviousResultā, add a variable that indicates the current cache state of the data, that way, I think all use cases mentioned could be solved like this:
Iām sorry if this comment comes off as āignorantā or āarrogantā, I didnāt spend to much time reading through the previous issues, so Iām not sure if I fully understand the problem described there.
But i simply used data.foo everywhere. I can do stuff, im a frontender. But itās not always good to do stuff. Especially when the query takes variables where i could decide to show part of old state and spinner or only a spinner. Now i have to hustle with data and i want to understand the rationale of the change.
@benjamn, unfortunately, it doesnāt work for me, I always have
previousData
asundefined
inuseLazyQuery
Hereās our current solution, seems to be working well. (Replaces
useQuery
.)The point is just to ensure that once
data
is defined, itās never reset toundefined
.Also if you donāt want a third party library, here is a custom hook that Iāve been using to track the previous value:
This accounts for the fact that
data
can beundefined
twice in a rowDrop in replacement for useQuery
@jfrolich ah š¤ hmmm interesting, it might not make a noticeable difference in this case, however I was following Dan Abramovās advice from here (near the bottom):
I think in my example the
useEffect
will update the value after the render has taken place, while if we remove it, it will update it during the rendering phase. Probably doesnāt matter much either way though šI know, I even participated in these discussions and critized this problem of AC2 š But now after upgrading to AC3, i saw that this behaviour is actually a useful default and that we relied on it in so many places.
having
previousData
would surely be a good solution. Having additionaly a flag to restore the old behaviour would also help developers to migrate, but for me,previousData
would be enough (tbh. i was expecting thatpreviousData
already exists)To mimic this, you can do this:
having
previousData
already in the result ofuseQuery
would be much cleaner.usePreviousDistinct(data, (p, next) => !next);
is cryptic. Unfortunatlyconst previousData = usePrevious(data)
does not work, altough this function exists inreact-use
. It will not work the same as AC2: when variables change while the previous query is still in-flight, it will updatepreviousData
value with undefinedJust wanted to note that:
using a ref a la @jfrolichās suggestion worked fine for me.
It would be great if this were better documented as a breaking change between v2 and v3 (if it was documented, i completely missed it). Iāll drop a comment in the appropriate issue.
@hueter: No need for a
useEffect
here!Iām not generally a fan of flags that let people avoid adapting their code, since the end result of that philosophy is death by a thousand flags, so itās good to hear that youād be open to
result.previousData
.I hadnāt seen
react-use
before, so thanks for letting me know that exists. It sounds like it could be a good tool for implementing workarounds like this, in some situations at least.In order to have our previous behavior and prevent unexpected issues. we implemented this hook. I hope this helps someone or open a discussion to find a better way.
@mdugue
Cheers mate, that is a nice trick. And more importantly that was the previous behaviour with v2.x anyway, right? so this works very nicely for anyone migrating to v3 and not wanting to mess too much to adapt to latest practices. So thanks!
Hereās a Typescript version of nathanredblur@'s hook.
Would it be possible to have
previousData
added touseMutation
as well? If so, Iād be happy to open a new issue for it.@oceandrama @xiaoyu-tamu Letās continue the discussion about
useLazyQuery
over at #7396. Thanks for bringing that problem to our attention.flags are bad, i know. But maybe its currently the best solution to have one global flag that restores the old behavior, so that people get a chance to migrate to apollo v3.
The change was added last minute where we already had a release candidate, so it took many by surprise. So maybe a compromise is necessary.
I have one project that uses apollo v2 and we decided not to update it until apollo v3 has a solution for that. I donāt want to ādecorateā every
useQuery
with ausePrevious
or similar or replaceuseQuery
with our own function that includesusePrevious
. I want to avoid that if possible.adding previousData would also be ok for me and is surely the better solution concerning api, but be aware that this still requires a lot of manual work and finding the places where this is needed is not that easy. It needs careful testing of the full application with every possible change of variables.