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
The issues it seems is that the onClick
handler in App
is doing two things:
client.resetStore();
when bool
is truebool
state update to toggle the value to falseWhat I suspect is happening is that the query client is reset immediately and the React state update is only enqueued and is processed sometime later by React. While the query client is reset and Test
is still mounted, another query request is fired off just before React processes the enqueued state update and triggers an App
component rerender which will then result in Test
unmounting.
Delay the client.restore
call by some small delay to be effected after the bool
state update has had a chance to be processed and the App
component tree rerendered.
export default function App() {
const [bool, setBool] = useState(false);
const client = useApolloClient();
return (
<main>
<button
onClick={() => {
setBool((bool) => {
return !bool;
});
setTimeout(() => {
if (bool) {
client.resetStore();
}
}, 100); // * Note
}}
>
Click me
</button>
{bool && <Test bool={bool} />}
</main>
);
}
*Note: This appears to work with a delay as small as 4-5ms, but used a higher value just in case. This is something you can tweak to suit your needs best.
Use an AbortController
and cancel any in-flight requests when Test
component unmounts.
export const Test = ({ bool }) => {
const abortController = useRef(new AbortController());
const { data, loading, error } = useQuery(testQuery, {
skip: !bool,
variables: { holy: "shit" },
context: {
fetchOptions: {
signal: abortController.current.signal,
}
},
});
useEffect(() => {
return () => {
abortController.current.abort("Component unmounted");
}
}, []);
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>
);
};