diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 3b4d16e7bc..ed64a9e448 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -1548,26 +1548,8 @@ export function buildHooks({ lastResult: UseQueryStateDefaultResult | undefined, queryArgs: any, ): UseQueryStateDefaultResult { - // if we had a last result and the current result is uninitialized, - // we might have called `api.util.resetApiState` - // in this case, reset the hook - if (lastResult?.endpointName && currentState.isUninitialized) { - const { endpointName } = lastResult - const endpointDefinition = endpointDefinitions[endpointName] - if ( - queryArgs !== skipToken && - serializeQueryArgs({ - queryArgs: lastResult.originalArgs, - endpointDefinition, - endpointName, - }) === - serializeQueryArgs({ - queryArgs, - endpointDefinition, - endpointName, - }) - ) - lastResult = undefined + if (shouldResetHookLastResult(currentState, lastResult, queryArgs)) { + lastResult = undefined } // data is the last known good request result we have tracked - or if none has been tracked yet the last good result for the current args @@ -1608,26 +1590,8 @@ export function buildHooks({ lastResult: UseInfiniteQueryStateDefaultResult | undefined, queryArgs: any, ): UseInfiniteQueryStateDefaultResult { - // if we had a last result and the current result is uninitialized, - // we might have called `api.util.resetApiState` - // in this case, reset the hook - if (lastResult?.endpointName && currentState.isUninitialized) { - const { endpointName } = lastResult - const endpointDefinition = endpointDefinitions[endpointName] - if ( - queryArgs !== skipToken && - serializeQueryArgs({ - queryArgs: lastResult.originalArgs, - endpointDefinition, - endpointName, - }) === - serializeQueryArgs({ - queryArgs, - endpointDefinition, - endpointName, - }) - ) - lastResult = undefined + if (shouldResetHookLastResult(currentState, lastResult, queryArgs)) { + lastResult = undefined } // data is the last known good request result we have tracked - or if none has been tracked yet the last good result for the current args @@ -1661,6 +1625,44 @@ export function buildHooks({ } as UseInfiniteQueryStateDefaultResult } + function shouldResetHookLastResult( + currentState: { isUninitialized: boolean }, + lastResult: + | { + endpointName?: string + isUninitialized: boolean + originalArgs?: any + } + | undefined, + queryArgs: any, + ) { + if (!currentState.isUninitialized || queryArgs === skipToken || !lastResult) + return false + + // If we had a last result and the current result is uninitialized, + // we might have called `api.util.resetApiState`, or the query cache entry + // might have been removed while a hook instance was skipped. + // In this case, reset the hook. + if (!lastResult.endpointName) { + return lastResult.isUninitialized + } + + const { endpointName } = lastResult + const endpointDefinition = endpointDefinitions[endpointName] + return ( + serializeQueryArgs({ + queryArgs: lastResult.originalArgs, + endpointDefinition, + endpointName, + }) === + serializeQueryArgs({ + queryArgs, + endpointDefinition, + endpointName, + }) + ) + } + function usePrefetch>( endpointName: EndpointName, defaultOptions?: PrefetchOptions, diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index 6cf025b901..6c02d8800a 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -4203,4 +4203,68 @@ describe('skip behavior', () => { currentData: undefined, }) }) + + test('unskipping a removed query cache entry restarts as an initial load', async () => { + const queryCacheKey = 'getIncrementedAmount(undefined)' + const { result, rerender } = renderHook( + (skip: boolean) => + api.endpoints.getIncrementedAmount.useQuery(undefined, { skip }), + { + wrapper: storeRef.wrapper, + initialProps: false, + }, + ) + + await act(async () => { + await waitForFakeTimer(150) + }) + + expect(result.current).toMatchObject({ + isLoading: false, + isSuccess: true, + data: { amount: 1 }, + }) + expect(storeRef.store.getState().api.queries[queryCacheKey]).toMatchObject({ + status: QueryStatus.fulfilled, + data: { amount: 1 }, + }) + + rerender(true) + await waitMs(1) + + expect(result.current).toMatchObject({ + status: QueryStatus.uninitialized, + isSuccess: true, + data: { amount: 1 }, + currentData: undefined, + }) + + await act(async () => { + await storeRef.store.dispatch( + api.endpoints.triggerUpdatedAmount.initiate(), + ) + }) + + expect(storeRef.store.getState().api.queries[queryCacheKey]).toBeUndefined() + + rerender(false) + + expect(result.current).toMatchObject({ + isLoading: true, + isFetching: true, + isSuccess: false, + data: undefined, + currentData: undefined, + }) + + await act(async () => { + await waitForFakeTimer(150) + }) + + expect(result.current).toMatchObject({ + isLoading: false, + isSuccess: true, + data: { amount: 2 }, + }) + }) })