question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

When variables change, data is the previous result in Query Component

See original GitHub issue

Continuing discussion from the bug apollographql/react-apollo#2202

Intended outcome: I want the data returned from useQuery to be undefined when the variables change. Reasoning is that if the variables for the query change, loading is set to true, but data remains set to the data from the previous query with old variables.

Actual outcome: data is set to the previous variables data 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:

  1. Adding a shouldInvalidatePreviousData option to useQuery. This would allow a developer to write code such as:
const { data, loading, error } = useQuery(FIND_DOGS, {
  variables: {
    search: searchText
  },
  shouldInvalidatePreviousData(nextVariables, previousVariables) {
    return nextVariables !== previousVariables;
  }
});
  1. Adding a clearPreviousDataOnLoad option to useQuery. This would allow a developer to write code such as:
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  }, [value]);
  return ref.current;
}

...

const previousSearchText = usePrevious(searchText);
const { data, loading, error } = useQuery(FIND_DOGS, {
  variables: {
    search: searchText
  },
  clearPreviousDataOnLoad: searchText !== previousSearchText
});
  1. A final option that I see as more of a workaround that requires no changes: https://codesandbox.io/s/cool-brown-ttwxw
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  }, [value]);
  return ref.current;
}

...

const previousSearchText = usePrevious(searchText);
const { data, loading, error } = useQuery(FIND_DOGS, {
  variables: {
    search: searchText
  }
});

const showDataWhileLoading = searchText === previousSearchText
const updatedData = !loading || showDataWhileLoading ? data : undefined;

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

System:
  OS: macOS 10.15.3
  Binaries:
    Node: 10.18.1 - /usr/local/bin/node
    Yarn: 1.21.1 - ~/.npm-global/bin/yarn
    npm: 6.13.1 - ~/.npm-global/bin/npm
  Browsers:
    Chrome: 80.0.3987.132
    Firefox: 69.0.2
    Safari: 13.0.5
  npmPackages:
    @apollo/client: ^3.0.0-beta.39 => 3.0.0-beta.39
    @apollo/react-components: 3.2.0-beta.0 => 3.2.0-beta.0
    @apollo/react-hoc: 3.2.0-beta.0 => 3.2.0-beta.0
    apollo-cache-persist: ^0.1.1 => 0.1.1
    apollo-client: ^2.6.4 => 2.6.8
    apollo-link-error: ^1.1.12 => 1.1.12
  npmGlobalPackages:
    apollo: 2.21.3```

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:8
  • Comments:11 (3 by maintainers)

github_iconTop GitHub Comments

21reactions
huetercommented, Jul 15, 2020

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 TableComponent above was just to change the opacity, so all of the previous data was still visible while loading. The <LoadingSpinner /> was supposed to be a fallback for basically the first page load only.

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 usePreviousNonNullish (inspired by this and this blog post) that keeps a ref to the prior version of a variable so I was able to re-implement this feature like so:

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

6reactions
benjamncommented, Jun 23, 2020

In general, I agree with @clayne11’s comment https://github.com/apollographql/react-apollo/pull/1639#issuecomment-465766827, and I would love to stop delivering previous data at all, without any special configuration around when it might be safe to deliver.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Queries - Apollo GraphQL Docs
When your component renders, useQuery returns an object from Apollo Client that contains loading , error , and data properties you can use...
Read more >
Use useQuery when a state variable changes - Stack Overflow
With useContext I make it accessible to all the components of the app: const EventProvider = ({ id, children }) => { const...
Read more >
Reactive Query | Vue Apollo
loadingKey will update the component data property you pass as the value. You should initialize this property to 0 in the component data()...
Read more >
urql Documentation - React API
Accepts as a plain string query or GraphQL DocumentNode. ... This function will be called with the previous data (or undefined ) and...
Read more >
Refreshing Queries - Relay
When referring to "refreshing a query", we mean fetching the exact same data that was originally rendered by the query, in order to...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found