-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
When variables change, data is the previous result in Query Component #6039
Comments
Because @hwillson had proposed the second option in apollographql/react-apollo#2889, I created a pull here: #6040 |
I have this same scenario |
I think sth like For interested, I made a small utility function in which you can wrap your import { NetworkStatus } from "apollo-client";
import { isEqual } from "apollo-utilities/lib/util/isEqual";
import { useRef } from "react";
import { QueryResult } from "react-apollo";
/**
* Use this if you want your `useQuery()` hook to not return previous query's result `data`
* after you change the `variables` to fetch a new query.
*
* This is temporary workaround until `@apollo/react-hooks` provides a solution for
* https://github.com/apollographql/apollo-client/issues/6039 .
*
* @example
* ```ts
* const { loading, data, refetch } = useClearPreviousDataOnVariablesChange(
* useGetUpcomingBillsQuery({
* variables: { reference }
* }),
* { reference }
* );
* ```
*/
export function useClearPreviousDataOnVariablesChange<TData, TVariables>(
result: QueryResult<TData, TVariables>,
variables: TVariables
): QueryResult<TData, TVariables> {
const prevResultRef = useRef(result);
const prevVariablesRef = useRef(variables);
const resultHasBeenLoadingRef = useRef(false);
// Variables have changed, but data is the same as it was for the previous variables
if (
!isEqual(prevVariablesRef.current, variables) &&
prevResultRef.current.data &&
prevResultRef.current.data === result.data
) {
// If the result is loading,
// return empty data and mark that result has been loading
if (result.loading) {
resultHasBeenLoadingRef.current = true;
return { ...result, data: undefined };
}
// If the result has loaded successfully,
// return it
if (
!result.loading &&
resultHasBeenLoadingRef.current &&
result.networkStatus === NetworkStatus.ready
) {
prevResultRef.current = result;
prevVariablesRef.current = variables;
resultHasBeenLoadingRef.current = false;
return result;
}
// If the result has failed to load,
// return empty data
if (
!result.loading &&
resultHasBeenLoadingRef.current &&
result.networkStatus === NetworkStatus.error
) {
return { ...result, data: undefined };
}
}
prevResultRef.current = result;
prevVariablesRef.current = variables;
resultHasBeenLoadingRef.current = false;
return result;
} |
@davismariotti Just to make sure I understand your expectations, would it be fair to say you never want Looking at your reproduction, I noticed you're using an object with One solution is to bless the const client = new ApolloClient({
link: new HttpLink({
uri: "https://www.graphqlhub.com/graphql"
}),
cache: new InMemoryCache({
typePolicies: {
RedditAPI: {
// This means the identity of the RedditAPI object doesn't depend on any
// of its fields other than its __typename, which allows the cache to merge
// any/all RedditAPI fields into the same singleton object.
keyFields: [],
},
},
}),
}); I realize that's not the answer to this particular issue, though it does improve the cache behavior of the reproduction. Just thought I'd mention it to make sure you're aware of your options here. |
In general, I agree with @clayne11's comment apollographql/react-apollo#1639 (comment), and I would love to stop delivering previous data at all, without any special configuration around when it might be safe to deliver. |
Results with loading:true can still provide data from the cache, but they should never provide data from a previous result, especially since the previous result may have been derived from entirely different variables: #6039 (comment) This is potentially a breaking change for code that relied on result.data not being undefined when result.loading is true, but the bugs that this change will fix are much more serious than the inconvenience of checking the truthiness of result.data before using it. Fixes #6039, as verified by the reproduction provided by @davismariotti: https://codesandbox.io/s/changing-variables-demo-obykj
Though this may be a breaking change, I believe it's really important to get this fixed before AC3 is released: #6566 |
Results with loading:true can still provide data from the cache, but they should never provide data from a previous result, especially since the previous result may have been derived from entirely different variables: #6039 (comment) This is potentially a breaking change for code that relied on result.data not being undefined when result.loading is true, but the bugs that this change will fix are much more serious than the inconvenience of checking the truthiness of result.data before using it. Fixes #6039, as verified by the reproduction provided by @davismariotti: https://codesandbox.io/s/changing-variables-demo-obykj
Hi all, I just wanted to add to this issue because although this change is reasonable, it reverses a major pattern from the past 2 years of Apollo client and there are probably a handful of apps like mine that didn't realize this behavior was unintentional until we upgraded and found this issue. In my app, we had deliberately used this behavior so the user could still view a table of data instead of a loading spinner. For example, consider: const { data, loading } = useQuery(EXAMPLE_QUERY, { variables: variablesFromQueryParams });
if (data) {
return <TableComponent loading={loading} contacts={data.contacts} />;
}
return <LoadingSpinner />; The loading state of the With #6566, now we see a loading spinner any time the query updates, no more seeing the opacity change on the existing data (as can be expected) Luckily in our repo we have a handy custom hook called Custom Hook export const usePreviousNonNullish = <T>(value: T): T => {
const ref = useRef<T>(value);
useEffect(() => {
if (value !== null && value !== undefined) {
ref.current = value;
}
});
return ref.current;
}; Usage const { data, loading } = useQuery(EXAMPLE_QUERY, { variables: variablesFromQueryParams });
const prevData = usePreviousNonNullish(data);
const contactData = data ?? prevData; // fall-back to the previous data while 'data' is undefined
if (contactData) {
return <TableComponent loading={loading} contacts={contactData.contacts} />;
}
return <LoadingSpinner />; So I mainly wanted to leave this example here in case anyone upgrades and their stuff isn't working anymore. But I also wanted to follow up with @benjamn because originally @davismariotti suggested adding a possible query option to preserve this behavior. Should I go ahead and track my own previous data or would this be something apollo could provide? Or maybe I'd be able to leverage the cache for this? Thank you 🙏, apologies for commenting on the closed issue |
thank you.
/ |
hi @macrozone that is a good point, I updated my comment and also replied to the other thread with a working solution that modifies the |
@benjamn Sorry to bring this back, but it seems like the problem is (rather ironically) not fixed for |
@benjamn In future design decisions I suggest looking at how react-query does it and imitate the design. In this case return old data until the new data arrives (which is generally the desired behavior in a UI that wants to avoid blinking or wait spinners). A flag can disable this behavior. It's just one flag. The UI must handle undefined data on initial load regardless, so there is no code adaptation necessary. |
Continuing discussion from the bug apollographql/react-apollo#2202
Intended outcome:
I want the
data
returned fromuseQuery
to be undefined when the variables change. Reasoning is that if the variables for the query change,loading
is set to true, butdata
remains set to the data from the previous query with old variables.Actual outcome:
data
is set to the previous variablesdata
while the next query (with new variables) is in flight.How to reproduce the issue:
https://codesandbox.io/s/changing-variables-demo-obykj
Discussion
While this may be intended and desired for most cases, I think it would be a good change to add the option for developers to choose if they want this behavior. One example (that caused me to find this issue) was a search bar that searches as you type, but doesn't search for anything (and hides results) if you've only typed less than 3 characters. My issue was that the user could search for something, delete everything, then re-search with different text. When the third character was pressed,
skip
would be set to false, and the search would happen, but there would be a brief moment of showing the data from the old query variables.I looked at apollographql/react-apollo#2889 by @jcready which had a potential fix for this issue, but looks abandoned. But following the discussion, a few possibilities were discussed as potential fixes to this issue:
shouldInvalidatePreviousData
option touseQuery
.This would allow a developer to write code such as:
clearPreviousDataOnLoad
option touseQuery
.This would allow a developer to write code such as:
https://codesandbox.io/s/cool-brown-ttwxw
I feel the third option is weird to write, and while the first option is probably the easiest from a developer's perspective, the second option would be the easiest to implement
Versions
The text was updated successfully, but these errors were encountered: