I have a weird case, where I need to allow for standard useQuery behavior, but at the same time need to run a piece of code only on the first onSuccess.
I know I can achieve this with useRef
, but is there a way around it?
I wasn't able to reproduce the issue 1:1, but you can see it after the first increment counter gets re-set to 100.
const [counter, setCounter] = useState(0);
const { data, isLoading } = useQuery(
["key"],
() => {
return axios.get("/200");
},
{
onSuccess() { // this runs twice
setCounter(100);
console.log("fetch");
}
}
);
return (
<div className="App">
<button
onClick={() => {
setCounter(counter + 1);
}}
>
counter {counter}
</button>
{data?.data && "data is present"}
</div>
);
=/src/App.tsx
I have a weird case, where I need to allow for standard useQuery behavior, but at the same time need to run a piece of code only on the first onSuccess.
I know I can achieve this with useRef
, but is there a way around it?
I wasn't able to reproduce the issue 1:1, but you can see it after the first increment counter gets re-set to 100.
const [counter, setCounter] = useState(0);
const { data, isLoading } = useQuery(
["key"],
() => {
return axios.get("https://picsum.photos/200");
},
{
onSuccess() { // this runs twice
setCounter(100);
console.log("fetch");
}
}
);
return (
<div className="App">
<button
onClick={() => {
setCounter(counter + 1);
}}
>
counter {counter}
</button>
{data?.data && "data is present"}
</div>
);
https://codesandbox.io/s/react-query-once-66lows?file=/src/App.tsx
Share Improve this question asked Oct 14, 2022 at 16:11 Alex IronsideAlex Ironside 5,07914 gold badges76 silver badges134 bronze badges 2-
I'm wondering what the use-case for something like this would be in practice? We have a
dataUpdateCount
internally that you could check for being equal to1
if we were to expose that on the result of useQuery. I'd just be curious to know why that would be needed ? – TkDodo Commented Oct 15, 2022 at 8:09 - @TkDodo I might be approaching this wrong, but long story short, I have an array of items that's returned from the b/e. The array is then built into a tree, which the user can edit. So first data returned from the query gets set as expanded tree elements (in our case 1 level deep), which the user can then close or open further. If I don't check, the expanded array will always change, since the user gets data from the b/e. I might have some re-render issues with the ponent, but until this is solved (assuming it's a re-render issue), I need a workaround. – Alex Ironside Commented Oct 15, 2022 at 18:40
5 Answers
Reset to default 2The easy hack is to add another boolean state like const [isRan, switchIsRan] = useState(false)
and then check its value in onSuccess()
before running any logic and also switch it to true
at the end of that callback.
Use lodash's once
Like this
const [counter, setCounter] = useState(0);
const onSuccess = _.once(() => {
setCounter(100);
console.log("fetch"));
}
const { data, isLoading } = useQuery(
["key"],
() => {
return axios.get("https://picsum.photos/200");
},
{
onSuccess
}
);
return (
<div className="App">
<button
onClick={() => {
setCounter(counter + 1);
}}
>
counter {counter}
</button>
{data?.data && "data is present"}
</div>
);
I run into a similar issue a few days ago and found this GH thread that suggest this solution that did the trick for me:
const [counter, setCounter] = useState(0);
const { data, isLoading } = useQuery(
["key"],
() => axios.get("https://picsum.photos/200").then(res => {
// This will only run once
setCounter(100);
return res;
})
);
return (
<div className="App">
<button
onClick={() => {
setCounter(counter + 1);
}}
>
counter {counter}
</button>
{data?.data && "data is present"}
</div>
);
After a while, we found a better solution. We can simply set staleTime
to Infinity
.
https://react-query-v3.tanstack./reference/useQuery#_top
This allows us to still invalidate the data and refetch if needed, but until it's invalidated, the query will not refetch
https://react-query-v3.tanstack./guides/query-invalidation#query-matching-with-invalidatequeries
We might also need to disable refetching on window focus
I had a similar need (query gets updated by user in WYSIWYG editor) and needed to get the initial data once only.
Instead of using the onSuccess callback, I did something like:
const [initialData, setInitialData] = useState(false);
const { data, isLoading } = useQuery(...);
// sets initialData if not set yet and data has been fetched
if (!initialData && !isLoading && data) {
setInitialData(data);
}