1- import util from 'util'
21import * as React from 'react'
32import type {
43 UseMutation ,
@@ -10,7 +9,14 @@ import {
109 QueryStatus ,
1110 skipToken ,
1211} from '@reduxjs/toolkit/query/react'
13- import { act , fireEvent , render , screen , waitFor } from '@testing-library/react'
12+ import {
13+ act ,
14+ fireEvent ,
15+ render ,
16+ screen ,
17+ waitFor ,
18+ renderHook ,
19+ } from '@testing-library/react'
1420import userEvent from '@testing-library/user-event'
1521import { rest } from 'msw'
1622import {
@@ -28,7 +34,6 @@ import type { AnyAction } from 'redux'
2834import type { SubscriptionOptions } from '@reduxjs/toolkit/dist/query/core/apiState'
2935import type { SerializedError } from '@reduxjs/toolkit'
3036import { createListenerMiddleware , configureStore } from '@reduxjs/toolkit'
31- import { renderHook } from '@testing-library/react'
3237import { delay } from '../../utils'
3338
3439// Just setup a temporary in-memory counter for tests that `getIncrementedAmount`.
@@ -715,6 +720,94 @@ describe('hooks tests', () => {
715720 expect ( res . data ! . amount ) . toBeGreaterThan ( originalAmount )
716721 } )
717722
723+ // See https://github.com/reduxjs/redux-toolkit/issues/3182
724+ test ( 'Hook subscriptions are properly cleaned up when changing skip back and forth' , async ( ) => {
725+ const pokemonApi = createApi ( {
726+ baseQuery : fetchBaseQuery ( { baseUrl : 'https://pokeapi.co/api/v2/' } ) ,
727+ endpoints : ( builder ) => ( {
728+ getPokemonByName : builder . query ( {
729+ queryFn : ( name : string ) => ( { data : null } ) ,
730+ keepUnusedDataFor : 1 ,
731+ } ) ,
732+ } ) ,
733+ } )
734+
735+ const storeRef = setupApiStore ( pokemonApi , undefined , {
736+ withoutTestLifecycles : true ,
737+ } )
738+
739+ const getSubscriptions = ( ) => storeRef . store . getState ( ) . api . subscriptions
740+
741+ const checkNumSubscriptions = ( arg : string , count : number ) => {
742+ const subscriptions = getSubscriptions ( )
743+ const cacheKeyEntry = subscriptions [ arg ]
744+
745+ if ( cacheKeyEntry ) {
746+ expect ( Object . values ( cacheKeyEntry ) . length ) . toBe ( count )
747+ }
748+ }
749+
750+ // 1) Initial state: an active subscription
751+ const { result, rerender, unmount } = renderHook (
752+ ( [ arg , options ] : Parameters <
753+ typeof pokemonApi . useGetPokemonByNameQuery
754+ > ) => pokemonApi . useGetPokemonByNameQuery ( arg , options ) ,
755+ {
756+ wrapper : storeRef . wrapper ,
757+ initialProps : [ 'a' ] ,
758+ }
759+ )
760+
761+ await act ( async ( ) => {
762+ await delay ( 1 )
763+ } )
764+
765+ // 2) Set the current subscription to `{skip: true}
766+ await act ( async ( ) => {
767+ rerender ( [ 'a' , { skip : true } ] )
768+ } )
769+
770+ // 3) Change _both_ the cache key _and_ `{skip: false}` at the same time.
771+ // This causes the `subscriptionRemoved` check to be `true`.
772+ await act ( async ( ) => {
773+ rerender ( [ 'b' ] )
774+ } )
775+
776+ // There should only be one active subscription after changing the arg
777+ checkNumSubscriptions ( 'b' , 1 )
778+
779+ // 4) Re-render with the same arg.
780+ // This causes the `subscriptionRemoved` check to be `false`.
781+ // Correct behavior is this does _not_ clear the promise ref,
782+ // so
783+ await act ( async ( ) => {
784+ rerender ( [ 'b' ] )
785+ } )
786+
787+ // There should only be one active subscription after changing the arg
788+ checkNumSubscriptions ( 'b' , 1 )
789+
790+ await act ( async ( ) => {
791+ await delay ( 1 )
792+ } )
793+
794+ unmount ( )
795+
796+ await act ( async ( ) => {
797+ await delay ( 1 )
798+ } )
799+
800+ // There should be no subscription entries left over after changing
801+ // cache key args and swapping `skip` on and off
802+ checkNumSubscriptions ( 'b' , 0 )
803+
804+ const finalSubscriptions = getSubscriptions ( )
805+
806+ for ( let cacheKeyEntry of Object . values ( finalSubscriptions ) ) {
807+ expect ( Object . values ( cacheKeyEntry ! ) . length ) . toBe ( 0 )
808+ }
809+ } )
810+
718811 describe ( 'Hook middleware requirements' , ( ) => {
719812 let mock : jest . SpyInstance
720813
0 commit comments