Skip to content

Commit 5c93a2e

Browse files
authored
fix: prevent missing renders (TanStack#1608)
1 parent ca947a6 commit 5c93a2e

File tree

4 files changed

+95
-36
lines changed

4 files changed

+95
-36
lines changed

src/react/tests/suspense.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe("useQuery's in Suspense mode", () => {
5959

6060
await sleep(20)
6161

62-
expect(renders).toBe(4)
62+
expect(renders).toBe(5)
6363
expect(states.length).toBe(2)
6464
expect(states[0]).toMatchObject({ data: 1, status: 'success' })
6565
expect(states[1]).toMatchObject({ data: 2, status: 'success' })

src/react/tests/useInfiniteQuery.test.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ describe('useInfiniteQuery', () => {
206206

207207
await sleep(300)
208208

209-
expect(states.length).toBe(6)
209+
expect(states.length).toBe(7)
210210
expect(states[0]).toMatchObject({
211211
data: undefined,
212212
isFetching: true,
@@ -243,7 +243,15 @@ describe('useInfiniteQuery', () => {
243243
isSuccess: true,
244244
isPreviousData: true,
245245
})
246+
// Hook state update
246247
expect(states[5]).toMatchObject({
248+
data: { pages: ['0-desc', '1-desc'] },
249+
isFetching: true,
250+
isFetchingNextPage: false,
251+
isSuccess: true,
252+
isPreviousData: true,
253+
})
254+
expect(states[6]).toMatchObject({
247255
data: { pages: ['0-asc'] },
248256
isFetching: false,
249257
isFetchingNextPage: false,
@@ -816,7 +824,7 @@ describe('useInfiniteQuery', () => {
816824

817825
await sleep(100)
818826

819-
expect(states.length).toBe(5)
827+
expect(states.length).toBe(6)
820828
expect(states[0]).toMatchObject({
821829
hasNextPage: undefined,
822830
data: undefined,
@@ -840,16 +848,24 @@ describe('useInfiniteQuery', () => {
840848
isFetchingNextPage: false,
841849
isSuccess: true,
842850
})
843-
// Refetch
851+
// Hook state update
844852
expect(states[3]).toMatchObject({
853+
hasNextPage: true,
854+
data: { pages: [7, 8] },
855+
isFetching: false,
856+
isFetchingNextPage: false,
857+
isSuccess: true,
858+
})
859+
// Refetch
860+
expect(states[4]).toMatchObject({
845861
hasNextPage: true,
846862
data: { pages: [7, 8] },
847863
isFetching: true,
848864
isFetchingNextPage: false,
849865
isSuccess: true,
850866
})
851867
// Refetch done
852-
expect(states[4]).toMatchObject({
868+
expect(states[5]).toMatchObject({
853869
hasNextPage: true,
854870
data: { pages: [7, 8] },
855871
isFetching: false,

src/react/tests/useQuery.test.tsx

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -568,15 +568,17 @@ describe('useQuery', () => {
568568

569569
await sleep(100)
570570

571-
expect(states.length).toBe(4)
571+
expect(states.length).toBe(5)
572572
// First load
573573
expect(states[0]).toMatchObject({ isLoading: true, isSuccess: false })
574574
// First success
575575
expect(states[1]).toMatchObject({ isLoading: false, isSuccess: true })
576-
// Second load
576+
// Remove
577577
expect(states[2]).toMatchObject({ isLoading: true, isSuccess: false })
578+
// Hook state update
579+
expect(states[3]).toMatchObject({ isLoading: true, isSuccess: false })
578580
// Second success
579-
expect(states[3]).toMatchObject({ isLoading: false, isSuccess: true })
581+
expect(states[4]).toMatchObject({ isLoading: false, isSuccess: true })
580582
})
581583

582584
it('should fetch when refetchOnMount is false and nothing has been fetched yet', async () => {
@@ -766,15 +768,17 @@ describe('useQuery', () => {
766768

767769
await sleep(20)
768770

769-
expect(states.length).toBe(4)
771+
expect(states.length).toBe(5)
770772
// Initial
771773
expect(states[0]).toMatchObject({ data: undefined })
772774
// Fetched
773775
expect(states[1]).toMatchObject({ data: 1 })
774-
// Switch
776+
// Remove
775777
expect(states[2]).toMatchObject({ data: undefined })
778+
// Hook state update
779+
expect(states[3]).toMatchObject({ data: undefined })
776780
// Fetched
777-
expect(states[3]).toMatchObject({ data: 2 })
781+
expect(states[4]).toMatchObject({ data: 2 })
778782
})
779783

780784
it('should be create a new query when refetching a removed query', async () => {
@@ -1080,7 +1084,7 @@ describe('useQuery', () => {
10801084

10811085
renderWithClient(queryClient, <Page />)
10821086

1083-
await waitFor(() => expect(states.length).toBe(4))
1087+
await waitFor(() => expect(states.length).toBe(5))
10841088

10851089
// Initial
10861090
expect(states[0]).toMatchObject({
@@ -1103,8 +1107,15 @@ describe('useQuery', () => {
11031107
isSuccess: true,
11041108
isPreviousData: true,
11051109
})
1106-
// New data
1110+
// Hook state update
11071111
expect(states[3]).toMatchObject({
1112+
data: 0,
1113+
isFetching: true,
1114+
isSuccess: true,
1115+
isPreviousData: true,
1116+
})
1117+
// New data
1118+
expect(states[4]).toMatchObject({
11081119
data: 1,
11091120
isFetching: false,
11101121
isSuccess: true,
@@ -1141,7 +1152,7 @@ describe('useQuery', () => {
11411152

11421153
renderWithClient(queryClient, <Page />)
11431154

1144-
await waitFor(() => expect(states.length).toBe(4))
1155+
await waitFor(() => expect(states.length).toBe(5))
11451156

11461157
// Initial
11471158
expect(states[0]).toMatchObject({
@@ -1164,8 +1175,15 @@ describe('useQuery', () => {
11641175
isSuccess: true,
11651176
isPreviousData: true,
11661177
})
1167-
// New data
1178+
// Hook state update
11681179
expect(states[3]).toMatchObject({
1180+
data: 0,
1181+
isFetching: true,
1182+
isSuccess: true,
1183+
isPreviousData: true,
1184+
})
1185+
// New data
1186+
expect(states[4]).toMatchObject({
11691187
data: 1,
11701188
isFetching: false,
11711189
isSuccess: true,
@@ -1210,7 +1228,7 @@ describe('useQuery', () => {
12101228

12111229
renderWithClient(queryClient, <Page />)
12121230

1213-
await waitFor(() => expect(states.length).toBe(6))
1231+
await waitFor(() => expect(states.length).toBe(7))
12141232

12151233
// Disabled query
12161234
expect(states[0]).toMatchObject({
@@ -1240,15 +1258,22 @@ describe('useQuery', () => {
12401258
isSuccess: true,
12411259
isPreviousData: true,
12421260
})
1243-
// Fetching new query
1261+
// Hook state update
12441262
expect(states[4]).toMatchObject({
1263+
data: 0,
1264+
isFetching: false,
1265+
isSuccess: true,
1266+
isPreviousData: true,
1267+
})
1268+
// Fetching new query
1269+
expect(states[5]).toMatchObject({
12451270
data: 0,
12461271
isFetching: true,
12471272
isSuccess: true,
12481273
isPreviousData: true,
12491274
})
12501275
// Fetched new query
1251-
expect(states[5]).toMatchObject({
1276+
expect(states[6]).toMatchObject({
12521277
data: 1,
12531278
isFetching: false,
12541279
isSuccess: true,
@@ -1299,7 +1324,7 @@ describe('useQuery', () => {
12991324

13001325
await sleep(100)
13011326

1302-
expect(states.length).toBe(5)
1327+
expect(states.length).toBe(6)
13031328

13041329
// Disabled query
13051330
expect(states[0]).toMatchObject({
@@ -1322,15 +1347,22 @@ describe('useQuery', () => {
13221347
isSuccess: true,
13231348
isPreviousData: true,
13241349
})
1325-
// Switched query key
1350+
// Hook state update
13261351
expect(states[3]).toMatchObject({
1352+
data: 10,
1353+
isFetching: false,
1354+
isSuccess: true,
1355+
isPreviousData: true,
1356+
})
1357+
// Refetch
1358+
expect(states[4]).toMatchObject({
13271359
data: 10,
13281360
isFetching: true,
13291361
isSuccess: true,
13301362
isPreviousData: true,
13311363
})
13321364
// Refetch done
1333-
expect(states[4]).toMatchObject({
1365+
expect(states[5]).toMatchObject({
13341366
data: 12,
13351367
isFetching: false,
13361368
isSuccess: true,
@@ -1690,6 +1722,27 @@ describe('useQuery', () => {
16901722
expect(renderedCount).toBe(2)
16911723
})
16921724

1725+
it('should render latest data even if react has discarded certain renders', async () => {
1726+
const key = queryKey()
1727+
1728+
function Page() {
1729+
const [, setNewState] = React.useState('state')
1730+
const state = useQuery(key, () => 'data')
1731+
React.useEffect(() => {
1732+
setActTimeout(() => {
1733+
queryClient.setQueryData(key, 'new')
1734+
// Update with same state to make react discard the next render
1735+
setNewState('state')
1736+
}, 10)
1737+
}, [])
1738+
return <div>{state.data}</div>
1739+
}
1740+
1741+
const rendered = renderWithClient(queryClient, <Page />)
1742+
1743+
await waitFor(() => rendered.getByText('new'))
1744+
})
1745+
16931746
// See https://github.com/tannerlinsley/react-query/issues/170
16941747
it('should start with status idle if enabled is false', async () => {
16951748
const key1 = queryKey()
@@ -2163,11 +2216,13 @@ describe('useQuery', () => {
21632216

21642217
await sleep(100)
21652218

2166-
expect(states.length).toBe(2)
2219+
expect(states.length).toBe(3)
21672220
// Initial
21682221
expect(states[0]).toMatchObject({ data: { count: 0 } })
21692222
// Set state
21702223
expect(states[1]).toMatchObject({ data: { count: 1 } })
2224+
// Hook state update
2225+
expect(states[2]).toMatchObject({ data: { count: 1 } })
21712226
})
21722227

21732228
it('should retry specified number of times', async () => {

src/react/useBaseQuery.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React from 'react'
22

33
import { notifyManager } from '../core/notifyManager'
44
import { QueryObserver } from '../core/queryObserver'
5-
import { QueryObserverResult } from '../core/types'
65
import { useQueryErrorResetBoundary } from './QueryErrorResetBoundary'
76
import { useQueryClient } from './QueryClientProvider'
87
import { UseBaseQueryOptions } from './types'
@@ -59,23 +58,12 @@ export function useBaseQuery<TQueryFnData, TError, TData, TQueryData>(
5958
}
6059

6160
const currentResult = observer.getCurrentResult()
62-
63-
// Remember latest result to prevent redundant renders
64-
const latestResultRef = React.useRef(currentResult)
65-
latestResultRef.current = currentResult
66-
67-
const [, rerender] = React.useState({})
61+
const [, setCurrentResult] = React.useState(currentResult)
6862

6963
// Subscribe to the observer
7064
React.useEffect(() => {
7165
errorResetBoundary.clearReset()
72-
return observer.subscribe(
73-
notifyManager.batchCalls((result: QueryObserverResult) => {
74-
if (result !== latestResultRef.current) {
75-
rerender({})
76-
}
77-
})
78-
)
66+
return observer.subscribe(notifyManager.batchCalls(setCurrentResult))
7967
}, [observer, errorResetBoundary])
8068

8169
// Handle suspense

0 commit comments

Comments
 (0)