reactjsreact-hooksapollo-clientgraphql-js

Apollo client cache not being reset after calling client.resetStore()


I am getting an unexpected behaviour. That is, I have put a skip condition in the query and even when the skip condition is supposed to be true, the query is being run. I have made a minimal reproduction of the bug

App.jsx:

import { useState } from "react";
import "./App.css";
import { Test } from "../test";
import { useApolloClient } from "@apollo/client";

export default function App() {
  const [bool, setBool] = useState(false);
  const client = useApolloClient();
  return (
    <main>
      <button
        onClick={() => {
            if (bool) {
              client.resetStore();
            }
          setBool((bool) => {
            return !bool;
          });
        }}
      >
        Click me
      </button>
      {bool && <Test bool={bool} />}
    </main>
  );
}

test.jsx:

import { gql, useQuery } from "@apollo/client";

const testQuery = gql`
  query {
    countries {
      name
    }
  }
`;
export const Test = ({ bool }) => {
  const { data, loading, error } = useQuery(testQuery, {
    skip: !bool,
    variables:{holy:"shit"}
  });
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error :(</p>;
  return (
    <div>
      {data.countries.map((country) => (
        <div key={country.name}>{country.name}</div>
      ))}
    </div>
  );
};

I am using the countries API provided by Apollo GraphQL.

Here, when the button is clicked for the first time, the query is sent to server that makes sense. Then when it is clicked again (ie. making bool=false) the cache should be cleared and the query should not run as it is being skipped, But it does run(I can see it's running by looking at the network tab). By doing conditional rendering of test I thought the query wouldn't run for sure as the component wouldn't even render but it was running again so I think the problem is that the query is running in between when the cache is cleared and ReactJS completely updates the state. I think I do not understand some concepts about states. How can I prevent the query from running when I dont want it to? Appreciate your help.

You can see it here for yourself replitLink


Solution

  • I think what is happening is when bool becomes falsy, the Test component instantly gets unmounted and the useQuery hook doesn't manage to register that the query is meant to be skipped. So for the useQuery hook, the value passed to skip is still false. Now, since Apollo refetches all active queries after calling resetStore() that query is also being refetched.

    The Docs in ApolloGraphQL states that

    It is important to remember that resetStore() will refetch any active queries. This means that any components that might be mounted will execute their queries again using your network interface. If you do not want to re-execute any queries then you should make sure to stop watching any active queries.

    So technically the query should not be called at all since the component is unmounted, so it might be a bug.

    I fixed the issue by not conditionally rendering the whole component rather just the ui of the component and let the query be skipped when bool is falsy.

    App.jsx:

    import { useEffect, useState } from "react";
    import "./App.css";
    import { Test } from "../test";
    import { useApolloClient } from "@apollo/client";
    
    export default function App() {
      const [bool, setBool] = useState(false);
      const client = useApolloClient();
      return (
        <main>
          <button
            onClick={() => {
              setBool((bool) => {
                client.resetStore()
                return !bool;
              });
            }}
          >
            Click me
          </button>
          <Test bool={bool} />
        </main>
      );
    }
    
    

    test.jsx:

    import { gql, useQuery } from "@apollo/client";
    
    const testQuery = gql`
      query {
        countries {
          name
        }
      }
    `;
    export const Test = ({ bool }) => {
      const { data, loading, error } = useQuery(testQuery, {
        skip: !bool,
        variables: { holy: "shit" },
      });
      if(!bool){
        return null
      }
      if (loading) return <p>Loading...</p>;
      if (error) return <p>Error :(</p>;
      return (
        <div>
          {data.countries.map((country) => (
            <div key={country.name}>{country.name}</div>
          ))}
        </div>
      );
    };