Skip to content

Commit 2885f69

Browse files
authored
Merge pull request #4651 from reduxjs/pr/fix-4650
call `initiate` to refetch queries from middleware
2 parents 8178e7f + 4788d51 commit 2885f69

File tree

6 files changed

+112
-15
lines changed

6 files changed

+112
-15
lines changed

packages/toolkit/src/query/core/buildMiddleware/index.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type {
2424
InternalMiddlewareState,
2525
} from './types'
2626
import { buildWindowEventHandler } from './windowEventHandling'
27+
import type { ApiEndpointQuery } from '../module'
2728
export type { ReferenceCacheCollection } from './cacheCollection'
2829
export type {
2930
MutationCacheLifecycleApi,
@@ -146,17 +147,10 @@ export function buildMiddleware<
146147
QuerySubState<any>,
147148
{ status: QueryStatus.uninitialized }
148149
>,
149-
queryCacheKey: string,
150-
override: Partial<QueryThunkArg> = {},
151150
) {
152-
return queryThunk({
153-
type: 'query',
154-
endpointName: querySubState.endpointName,
155-
originalArgs: querySubState.originalArgs,
151+
return (input.api.endpoints[querySubState.endpointName] as ApiEndpointQuery<any, any>).initiate(querySubState.originalArgs as any, {
156152
subscribe: false,
157153
forceRefetch: true,
158-
queryCacheKey: queryCacheKey as any,
159-
...override,
160154
})
161155
}
162156
}

packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({
122122
}),
123123
)
124124
} else if (querySubState.status !== QueryStatus.uninitialized) {
125-
mwApi.dispatch(refetchQuery(querySubState, queryCacheKey))
125+
mwApi.dispatch(refetchQuery(querySubState))
126126
}
127127
}
128128
}

packages/toolkit/src/query/core/buildMiddleware/polling.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export const buildPollingHandler: InternalHandlerBuilder = ({
7878
pollingInterval: lowestPollingInterval,
7979
timeout: setTimeout(() => {
8080
if (state.config.focused || !skipPollingIfUnfocused) {
81-
api.dispatch(refetchQuery(querySubState, queryCacheKey))
81+
api.dispatch(refetchQuery(querySubState))
8282
}
8383
startNextPoll({ queryCacheKey }, api)
8484
}, lowestPollingInterval),

packages/toolkit/src/query/core/buildMiddleware/types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
AsyncThunkAction,
44
Middleware,
55
MiddlewareAPI,
6+
ThunkAction,
67
ThunkDispatch,
78
UnknownAction,
89
} from '@reduxjs/toolkit'
@@ -23,6 +24,7 @@ import type {
2324
QueryThunkArg,
2425
ThunkResult,
2526
} from '../buildThunks'
27+
import type { QueryActionCreatorResult } from '../buildInitiate'
2628

2729
export type QueryStateMeta<T> = Record<string, undefined | T>
2830
export type TimeoutId = ReturnType<typeof setTimeout>
@@ -62,10 +64,8 @@ export interface BuildSubMiddlewareInput
6264
querySubState: Exclude<
6365
QuerySubState<any>,
6466
{ status: QueryStatus.uninitialized }
65-
>,
66-
queryCacheKey: string,
67-
override?: Partial<QueryThunkArg>,
68-
): AsyncThunkAction<ThunkResult, QueryThunkArg, {}>
67+
>
68+
): ThunkAction<QueryActionCreatorResult<any>, any, any, UnknownAction>
6969
isThisApiSliceAction: (action: Action) => boolean
7070
}
7171

packages/toolkit/src/query/core/buildMiddleware/windowEventHandling.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const buildWindowEventHandler: InternalHandlerBuilder = ({
5858
}),
5959
)
6060
} else if (querySubState.status !== QueryStatus.uninitialized) {
61-
api.dispatch(refetchQuery(querySubState, queryCacheKey))
61+
api.dispatch(refetchQuery(querySubState))
6262
}
6363
}
6464
}

packages/toolkit/src/query/tests/buildHooks.test.tsx

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const api = createApi({
7373
data: arg?.body ? { ...arg.body, ...(amount ? { amount } : {}) } : {},
7474
}
7575
},
76+
tagTypes: ['IncrementedAmount'],
7677
endpoints: (build) => ({
7778
getUser: build.query<{ name: string }, number>({
7879
query: () => ({
@@ -93,6 +94,13 @@ const api = createApi({
9394
amount,
9495
},
9596
}),
97+
providesTags: ['IncrementedAmount'],
98+
}),
99+
triggerUpdatedAmount: build.mutation<void, void>({
100+
queryFn: async () => {
101+
return { data: undefined }
102+
},
103+
invalidatesTags: ['IncrementedAmount'],
96104
}),
97105
updateUser: build.mutation<{ name: string }, { name: string }>({
98106
query: (update) => ({ body: update }),
@@ -1375,6 +1383,101 @@ describe('hooks tests', () => {
13751383

13761384
expect(screen.getByTestId('error').textContent).toBe('')
13771385
})
1386+
1387+
test('useLazyQuery trigger promise returns the correctly updated data', async () => {
1388+
const LazyUnwrapUseEffect = () => {
1389+
const [
1390+
triggerGetIncrementedAmount,
1391+
{ isFetching, isSuccess, isError, error, data },
1392+
] = api.endpoints.getIncrementedAmount.useLazyQuery()
1393+
1394+
type AmountData = { amount: number } | undefined
1395+
1396+
const [triggerUpdate] = api.endpoints.triggerUpdatedAmount.useMutation()
1397+
1398+
const [dataFromQuery, setDataFromQuery] =
1399+
useState<AmountData>(undefined)
1400+
const [dataFromTrigger, setDataFromTrigger] =
1401+
useState<AmountData>(undefined)
1402+
1403+
const handleLoad = async () => {
1404+
try {
1405+
const res = await triggerGetIncrementedAmount().unwrap()
1406+
1407+
setDataFromTrigger(res) // adding client side state here will cause stale data
1408+
} catch (error) {
1409+
console.error(error)
1410+
}
1411+
}
1412+
1413+
const handleMutate = async () => {
1414+
try {
1415+
await triggerUpdate()
1416+
// Force the lazy trigger to refetch
1417+
await handleLoad()
1418+
} catch (error) {
1419+
console.error(error)
1420+
}
1421+
}
1422+
1423+
useEffect(() => {
1424+
// Intentionally copy to local state for comparison purposes
1425+
setDataFromQuery(data)
1426+
}, [data])
1427+
1428+
let content: React.ReactNode | null = null
1429+
1430+
if (isFetching) {
1431+
content = <div className="loading">Loading</div>
1432+
} else if (isSuccess) {
1433+
content = (
1434+
<div className="wrapper">
1435+
<div>
1436+
useEffect data: {dataFromQuery?.amount ?? 'No query amount'}
1437+
</div>
1438+
<div>
1439+
Unwrap data: {dataFromTrigger?.amount ?? 'No trigger amount'}
1440+
</div>
1441+
</div>
1442+
)
1443+
}
1444+
1445+
return (
1446+
<div className="outer">
1447+
<button onClick={() => handleLoad()}>Load Data</button>
1448+
<button onClick={() => handleMutate()}>Update Data</button>
1449+
{content}
1450+
</div>
1451+
)
1452+
}
1453+
1454+
render(<LazyUnwrapUseEffect />, { wrapper: storeRef.wrapper })
1455+
1456+
// Kick off the initial fetch via lazy query trigger
1457+
act(() => {
1458+
userEvent.click(screen.getByText('Load Data'))
1459+
})
1460+
1461+
// We get back initial data, which should get copied into local state,
1462+
// and also should come back as valid via the lazy trigger promise
1463+
await waitFor(() => {
1464+
expect(screen.getByText('useEffect data: 1')).toBeTruthy()
1465+
expect(screen.getByText('Unwrap data: 1')).toBeTruthy()
1466+
})
1467+
1468+
// If we mutate and then re-run the lazy trigger afterwards...
1469+
act(() => {
1470+
userEvent.click(screen.getByText('Update Data'))
1471+
})
1472+
1473+
// We should see both sets of data agree (ie, the lazy trigger promise
1474+
// should not return stale data or be out of sync with the hook).
1475+
// Prior to PR #4651, this would fail because the trigger never updated properly.
1476+
await waitFor(() => {
1477+
expect(screen.getByText('useEffect data: 2')).toBeTruthy()
1478+
expect(screen.getByText('Unwrap data: 2')).toBeTruthy()
1479+
})
1480+
})
13781481
})
13791482

13801483
describe('useMutation', () => {

0 commit comments

Comments
 (0)