query: too many re-renders when changing keys
Describe the bug
When changing the query key, react-query re-renders the component two times with the exact same states
Your minimal, reproducible example
v4: https://codesandbox.io/s/react-query-re-renders-t2z4u8, v3: https://codesandbox.io/s/react-query-re-renders-v3-cfo8y4
Steps to reproduce
- Open the sandbox and inspect the console
- on page load, you’ll see two renders (loading and success): 🆗
- click the
nextbutton - you’ll see 3 additional renders
- loading: 🆗
- loading: 🚫
- success: 🆗
The second “loading” render has the exact same state as the previous one and is unnecessary.
This happens on v17 and v18 of react, and in v3 and v4 of react-query. It also has nothing to do with extra renders in dev-mode and strict effects (the example doesn’t use strict mode), and it can also be reproduced when the logging is moved to an effect. The component really renders twice.
Expected behavior
we should see two renders instead of three:
- loading: 🆗
- success: 🆗
How often does this bug happen?
Every time
Screenshots or Videos
Platform
independent (I’m on macos & brave)
react-query version
v3 and v4
TypeScript version
No response
Additional context
first reported on stackoverflow: https://stackoverflow.com/questions/72834988/react-query-makes-component-to-rerender-multiple-times
my best guess is that setState triggers one render, and then useQuery re-renders again. But those should be deduped…
here’s a failing test-case to reproduce:
it('should not render too often when query key changes', async () => {
const key = queryKey()
const states: UseQueryResult<{ count: number }>[] = []
function Page() {
const [count, setCount] = React.useState(0)
const state = useQuery([key, count], async () => {
await sleep(10)
return { count }
})
states.push(state)
return (
<div>
<h1>data: {state.data?.count}</h1>
<button
onClick={() => {
setCount(prev => prev + 1);
}}
>
Next
</button>
</div>
);
}
const rendered = renderWithClient(queryClient, <Page />)
await waitFor(() => rendered.getByText('data: 0'))
// Initial
expect(states[0]).toMatchObject({ status: 'loading', data: undefined })
// loaded
expect(states[1]).toMatchObject({ status: 'success', data: { count: 0 } })
expect(states.length).toBe(2)
fireEvent.click(rendered.getByRole('button', { name: /next/i }))
await waitFor(() => rendered.getByText('data: 1'))
// fetching with new key
expect(states[2]).toMatchObject({ status: 'loading', data: undefined })
// loaded
expect(states[3]).toMatchObject({ status: 'success', data: { count: 0 } })
expect(states.length).toBe(4)
})
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 12
- Comments: 23 (1 by maintainers)
I think I have solved the problem. It was with the dependency array in the useEffect hook. Try giving an empty dependency array.
solution by having an empty dependency array
problem by not having an empty dependency array
it should be fixed when the bugs in react are fixed 🤷
remove
React.StrictModefrom the example and the render count goes down to 2. This is not related.Is this issue fixed? I’m facing this issue currently. The loading and success state causing re-render twice
amazing @incepter 🙌
I think the same issue is addressed in https://github.com/TanStack/query/issues/5538; and this PR may solve it.
Here is the same sandbox with the built version from the PR that shows the resolve actually: https://codesandbox.io/s/react-query-re-renders-forked-66s9td?file=/src/App.tsx
@karol-janik I’m not sure we can prevent/memo the change.
Might be easier to not fire the “fetch” event from react-query observer when the key changes since this is an extra event that causes the extra “loading” state to appear.
Note: this used to work in
3.5.11, but no longer in3.5.12see: