From a59476efe762f21adb79ea68092f8aa185c970df Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Sun, 6 Oct 2024 21:27:23 +0200 Subject: [PATCH 1/2] call `initiate` to refetch queries from middleware --- .../toolkit/src/query/core/buildMiddleware/index.ts | 10 ++-------- .../query/core/buildMiddleware/invalidationByTags.ts | 2 +- .../toolkit/src/query/core/buildMiddleware/polling.ts | 2 +- .../toolkit/src/query/core/buildMiddleware/types.ts | 8 ++++---- .../query/core/buildMiddleware/windowEventHandling.ts | 2 +- 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/packages/toolkit/src/query/core/buildMiddleware/index.ts b/packages/toolkit/src/query/core/buildMiddleware/index.ts index 737b2fa533..17092d82fd 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/index.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/index.ts @@ -24,6 +24,7 @@ import type { InternalMiddlewareState, } from './types' import { buildWindowEventHandler } from './windowEventHandling' +import type { ApiEndpointQuery } from '../module' export type { ReferenceCacheCollection } from './cacheCollection' export type { MutationCacheLifecycleApi, @@ -146,17 +147,10 @@ export function buildMiddleware< QuerySubState, { status: QueryStatus.uninitialized } >, - queryCacheKey: string, - override: Partial = {}, ) { - return queryThunk({ - type: 'query', - endpointName: querySubState.endpointName, - originalArgs: querySubState.originalArgs, + return (input.api.endpoints[querySubState.endpointName] as ApiEndpointQuery).initiate(querySubState.originalArgs as any, { subscribe: false, forceRefetch: true, - queryCacheKey: queryCacheKey as any, - ...override, }) } } diff --git a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts index 150406a863..7dda07a7f2 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts @@ -122,7 +122,7 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ }), ) } else if (querySubState.status !== QueryStatus.uninitialized) { - mwApi.dispatch(refetchQuery(querySubState, queryCacheKey)) + mwApi.dispatch(refetchQuery(querySubState)) } } } diff --git a/packages/toolkit/src/query/core/buildMiddleware/polling.ts b/packages/toolkit/src/query/core/buildMiddleware/polling.ts index 6bee83fae4..dbf3a8533b 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/polling.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/polling.ts @@ -78,7 +78,7 @@ export const buildPollingHandler: InternalHandlerBuilder = ({ pollingInterval: lowestPollingInterval, timeout: setTimeout(() => { if (state.config.focused || !skipPollingIfUnfocused) { - api.dispatch(refetchQuery(querySubState, queryCacheKey)) + api.dispatch(refetchQuery(querySubState)) } startNextPoll({ queryCacheKey }, api) }, lowestPollingInterval), diff --git a/packages/toolkit/src/query/core/buildMiddleware/types.ts b/packages/toolkit/src/query/core/buildMiddleware/types.ts index ecf40d23f0..e24612748e 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/types.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/types.ts @@ -3,6 +3,7 @@ import type { AsyncThunkAction, Middleware, MiddlewareAPI, + ThunkAction, ThunkDispatch, UnknownAction, } from '@reduxjs/toolkit' @@ -23,6 +24,7 @@ import type { QueryThunkArg, ThunkResult, } from '../buildThunks' +import type { QueryActionCreatorResult } from '../buildInitiate' export type QueryStateMeta = Record export type TimeoutId = ReturnType @@ -62,10 +64,8 @@ export interface BuildSubMiddlewareInput querySubState: Exclude< QuerySubState, { status: QueryStatus.uninitialized } - >, - queryCacheKey: string, - override?: Partial, - ): AsyncThunkAction + > + ): ThunkAction, any, any, UnknownAction> isThisApiSliceAction: (action: Action) => boolean } diff --git a/packages/toolkit/src/query/core/buildMiddleware/windowEventHandling.ts b/packages/toolkit/src/query/core/buildMiddleware/windowEventHandling.ts index d59df2eaa5..e3c17b6513 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/windowEventHandling.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/windowEventHandling.ts @@ -58,7 +58,7 @@ export const buildWindowEventHandler: InternalHandlerBuilder = ({ }), ) } else if (querySubState.status !== QueryStatus.uninitialized) { - api.dispatch(refetchQuery(querySubState, queryCacheKey)) + api.dispatch(refetchQuery(querySubState)) } } } From 4788d5188f8093424441d250d2d763646d975eff Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 7 Oct 2024 22:08:17 -0400 Subject: [PATCH 2/2] Add test for lazy query trigger fix --- .../src/query/tests/buildHooks.test.tsx | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index 5c1985985b..5ec6b32c14 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -73,6 +73,7 @@ const api = createApi({ data: arg?.body ? { ...arg.body, ...(amount ? { amount } : {}) } : {}, } }, + tagTypes: ['IncrementedAmount'], endpoints: (build) => ({ getUser: build.query<{ name: string }, number>({ query: () => ({ @@ -93,6 +94,13 @@ const api = createApi({ amount, }, }), + providesTags: ['IncrementedAmount'], + }), + triggerUpdatedAmount: build.mutation({ + queryFn: async () => { + return { data: undefined } + }, + invalidatesTags: ['IncrementedAmount'], }), updateUser: build.mutation<{ name: string }, { name: string }>({ query: (update) => ({ body: update }), @@ -1375,6 +1383,101 @@ describe('hooks tests', () => { expect(screen.getByTestId('error').textContent).toBe('') }) + + test('useLazyQuery trigger promise returns the correctly updated data', async () => { + const LazyUnwrapUseEffect = () => { + const [ + triggerGetIncrementedAmount, + { isFetching, isSuccess, isError, error, data }, + ] = api.endpoints.getIncrementedAmount.useLazyQuery() + + type AmountData = { amount: number } | undefined + + const [triggerUpdate] = api.endpoints.triggerUpdatedAmount.useMutation() + + const [dataFromQuery, setDataFromQuery] = + useState(undefined) + const [dataFromTrigger, setDataFromTrigger] = + useState(undefined) + + const handleLoad = async () => { + try { + const res = await triggerGetIncrementedAmount().unwrap() + + setDataFromTrigger(res) // adding client side state here will cause stale data + } catch (error) { + console.error(error) + } + } + + const handleMutate = async () => { + try { + await triggerUpdate() + // Force the lazy trigger to refetch + await handleLoad() + } catch (error) { + console.error(error) + } + } + + useEffect(() => { + // Intentionally copy to local state for comparison purposes + setDataFromQuery(data) + }, [data]) + + let content: React.ReactNode | null = null + + if (isFetching) { + content =
Loading
+ } else if (isSuccess) { + content = ( +
+
+ useEffect data: {dataFromQuery?.amount ?? 'No query amount'} +
+
+ Unwrap data: {dataFromTrigger?.amount ?? 'No trigger amount'} +
+
+ ) + } + + return ( +
+ + + {content} +
+ ) + } + + render(, { wrapper: storeRef.wrapper }) + + // Kick off the initial fetch via lazy query trigger + act(() => { + userEvent.click(screen.getByText('Load Data')) + }) + + // We get back initial data, which should get copied into local state, + // and also should come back as valid via the lazy trigger promise + await waitFor(() => { + expect(screen.getByText('useEffect data: 1')).toBeTruthy() + expect(screen.getByText('Unwrap data: 1')).toBeTruthy() + }) + + // If we mutate and then re-run the lazy trigger afterwards... + act(() => { + userEvent.click(screen.getByText('Update Data')) + }) + + // We should see both sets of data agree (ie, the lazy trigger promise + // should not return stale data or be out of sync with the hook). + // Prior to PR #4651, this would fail because the trigger never updated properly. + await waitFor(() => { + expect(screen.getByText('useEffect data: 2')).toBeTruthy() + expect(screen.getByText('Unwrap data: 2')).toBeTruthy() + }) + }) }) describe('useMutation', () => {