diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 3b4d16e7bc..c88ce6f795 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -1547,25 +1547,31 @@ export function buildHooks({ currentState: QueryResultSelectorResult, lastResult: UseQueryStateDefaultResult | undefined, queryArgs: any, + lastResultForReset: UseQueryStateDefaultResult | undefined, + lastResultState: QuerySubState | undefined, ): 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 resetResult = + queryArgs === skipToken ? lastResultForReset : lastResult + if (resetResult?.endpointName && currentState.isUninitialized) { + const { endpointName } = resetResult const endpointDefinition = endpointDefinitions[endpointName] if ( - queryArgs !== skipToken && - serializeQueryArgs({ - queryArgs: lastResult.originalArgs, - endpointDefinition, - endpointName, - }) === - serializeQueryArgs({ - queryArgs, - endpointDefinition, - endpointName, - }) + queryArgs === skipToken + ? !lastResultState || + lastResultState.status === QueryStatus.uninitialized + : serializeQueryArgs({ + queryArgs: resetResult.originalArgs, + endpointDefinition, + endpointName, + }) === + serializeQueryArgs({ + queryArgs, + endpointDefinition, + endpointName, + }) ) lastResult = undefined } @@ -1607,25 +1613,31 @@ export function buildHooks({ currentState: InfiniteQueryResultSelectorResult, lastResult: UseInfiniteQueryStateDefaultResult | undefined, queryArgs: any, + lastResultForReset: UseInfiniteQueryStateDefaultResult | undefined, + lastResultState: InfiniteQuerySubState | undefined, ): 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 resetResult = + queryArgs === skipToken ? lastResultForReset : lastResult + if (resetResult?.endpointName && currentState.isUninitialized) { + const { endpointName } = resetResult const endpointDefinition = endpointDefinitions[endpointName] if ( - queryArgs !== skipToken && - serializeQueryArgs({ - queryArgs: lastResult.originalArgs, - endpointDefinition, - endpointName, - }) === - serializeQueryArgs({ - queryArgs, - endpointDefinition, - endpointName, - }) + queryArgs === skipToken + ? !lastResultState || + lastResultState.status === QueryStatus.uninitialized + : serializeQueryArgs({ + queryArgs: resetResult.originalArgs, + endpointDefinition, + endpointName, + }) === + serializeQueryArgs({ + queryArgs, + endpointDefinition, + endpointName, + }) ) lastResult = undefined } @@ -1846,10 +1858,12 @@ export function buildHooks({ const stableArg = useStableQueryArgs(skip ? skipToken : arg) type ApiRootState = Parameters>[0] + type SelectorWithLastResult = Selector const lastValue = useRef(undefined) + const lastValueForReset = useRef(undefined) - const selectDefaultResult: Selector = useMemo( + const selectDefaultResult: SelectorWithLastResult = useMemo( () => // Normally ts-ignores are bad and should be avoided, but we're // already casting this selector to be `Selector` anyway, @@ -1861,6 +1875,29 @@ export function buildHooks({ select(stableArg), (_: ApiRootState, lastResult: any) => lastResult, (_: ApiRootState) => stableArg, + (_: ApiRootState, _lastResult: any, lastResultForReset: any) => + lastResultForReset, + ( + state: ApiRootState, + _lastResult: any, + lastResultForReset: any, + ) => { + if ( + stableArg === skipToken && + lastResultForReset?.endpointName === endpointName + ) { + const endpointDefinition = endpointDefinitions[endpointName] + const queryCacheKey = serializeQueryArgs({ + queryArgs: lastResultForReset.originalArgs, + endpointDefinition, + endpointName, + }) + + return state[api.reducerPath]?.queries?.[queryCacheKey] + } + + return undefined + }, ], preSelector, { @@ -1872,7 +1909,7 @@ export function buildHooks({ [select, stableArg], ) - const querySelector: Selector = useMemo( + const querySelector: SelectorWithLastResult = useMemo( () => selectFromResult ? createSelector([selectDefaultResult], selectFromResult, { @@ -1884,7 +1921,7 @@ export function buildHooks({ const currentState = useSelector( (state: RootState) => - querySelector(state, lastValue.current), + querySelector(state, lastValue.current, lastValueForReset.current), shallowEqual, ) @@ -1892,9 +1929,13 @@ export function buildHooks({ const newLastValue = selectDefaultResult( store.getState(), lastValue.current, + lastValueForReset.current, ) useIsomorphicLayoutEffect(() => { lastValue.current = newLastValue + if (newLastValue?.endpointName) { + lastValueForReset.current = newLastValue + } }, [newLastValue]) return currentState diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index 6cf025b901..5e5f3c9249 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -903,6 +903,40 @@ describe('hooks tests', () => { }) }) + test('clears retained data when resetApiState is dispatched while skipped', async () => { + const { result, rerender } = renderHook( + ([arg, skip]: [number, boolean]) => + api.endpoints.getUser.useQuery(arg, { skip }), + { + wrapper: storeRef.wrapper, + initialProps: [5, false] as [number, boolean], + }, + ) + + await waitFor(() => expect(result.current.isSuccess).toBe(true)) + expect(result.current.data).toEqual({ name: 'Timmy' }) + + rerender([5, true]) + + expect(result.current).toMatchObject({ + status: QueryStatus.uninitialized, + isSuccess: true, + data: { name: 'Timmy' }, + currentData: undefined, + }) + + act(() => void storeRef.store.dispatch(api.util.resetApiState())) + + await waitFor(() => { + expect(result.current).toMatchObject({ + status: QueryStatus.uninitialized, + isSuccess: false, + data: undefined, + currentData: undefined, + }) + }) + }) + test('hook should not be stuck loading post resetApiState after re-render', async () => { const user = userEvent.setup()