Reputation: 5049
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
Upvotes: 1
Views: 4755
Reputation: 5049
After a while, we found a better solution. We can simply set staleTime
to Infinity
.
https://react-query-v3.tanstack.com/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.com/guides/query-invalidation#query-matching-with-invalidatequeries
We might also need to disable refetching on window focus
Upvotes: 1
Reputation: 81
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>
);
Upvotes: 2
Reputation: 89
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);
}
Upvotes: -1
Reputation: 39
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>
);
Upvotes: 2
Reputation: 323
The 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.
Upvotes: 2