Skip to content

Commit 41ecb92

Browse files
committed
Merge branch 'master' of https://github.com/reduxjs/redux-toolkit into update-ts-to-5.4
2 parents 17bf9ed + 1afcdd4 commit 41ecb92

File tree

7 files changed

+81
-8
lines changed

7 files changed

+81
-8
lines changed

docs/api/autoBatchEnhancer.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ Any action that is tagged with `action.meta[SHOULD_AUTOBATCH] = true` will be tr
9999

100100
- `{type: 'raf'}`: queues using `requestAnimationFrame` (default)
101101
- `{type: 'tick'}`: queues using `queueMicrotask`
102-
- `{type: 'timer, timeout: number}`: queues using `setTimeout`
102+
- `{type: 'timer', timeout: number}`: queues using `setTimeout`
103103
- `{type: 'callback', queueNotification: (notify: () => void) => void}`: lets you provide your own callback, such as a debounced or throttled function
104104

105105
The default behavior is to queue the notifications using `requestAnimationFrame`.

errors.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,6 @@
3737
"35": "Existing Redux context detected. If you already have a store set up, please use the traditional Redux setup.",
3838
"36": "When using custom hooks for context, all hooks need to be provided: .\\nHook was either not provided or not a function.",
3939
"37": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\n You must add the middleware for RTK-Query to function correctly!",
40-
"38": "Cannot refetch a query that has not been started yet."
41-
}
40+
"38": "Cannot refetch a query that has not been started yet.",
41+
"39": "called \\`injectEndpoints\\` to override already-existing endpointName without specifying \\`overrideExisting: true\\`"
42+
}

packages/toolkit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@reduxjs/toolkit",
3-
"version": "2.2.1",
3+
"version": "2.2.2",
44
"description": "The official, opinionated, batteries-included toolset for efficient Redux development",
55
"author": "Mark Erikson <[email protected]>",
66
"license": "MIT",

packages/toolkit/src/autoBatchEnhancer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export type AutoBatchOptions =
4444
* By default, it will queue a notification for the end of the event loop tick.
4545
* However, you can pass several other options to configure the behavior:
4646
* - `{type: 'tick'}`: queues using `queueMicrotask`
47-
* - `{type: 'timer, timeout: number}`: queues using `setTimeout`
47+
* - `{type: 'timer', timeout: number}`: queues using `setTimeout`
4848
* - `{type: 'raf'}`: queues using `requestAnimationFrame` (default)
4949
* - `{type: 'callback', queueNotification: (notify: () => void) => void}`: lets you provide your own callback
5050
*

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isAnyOf } from '@reduxjs/toolkit'
12
import type { BaseQueryFn } from '../../baseQueryTypes'
23
import type { QueryDefinition } from '../../endpointDefinitions'
34
import type { ConfigState, QueryCacheKey } from '../apiState'
@@ -49,11 +50,18 @@ export const THIRTY_TWO_BIT_MAX_TIMER_SECONDS = 2_147_483_647 / 1_000 - 1
4950
export const buildCacheCollectionHandler: InternalHandlerBuilder = ({
5051
reducerPath,
5152
api,
53+
queryThunk,
5254
context,
5355
internalState,
5456
}) => {
5557
const { removeQueryResult, unsubscribeQueryResult } = api.internalActions
5658

59+
const canTriggerUnsubscribe = isAnyOf(
60+
unsubscribeQueryResult.match,
61+
queryThunk.fulfilled,
62+
queryThunk.rejected
63+
)
64+
5765
function anySubscriptionsRemainingForKey(queryCacheKey: string) {
5866
const subscriptions = internalState.currentSubscriptions[queryCacheKey]
5967
return !!subscriptions && !isObjectEmpty(subscriptions)
@@ -66,9 +74,11 @@ export const buildCacheCollectionHandler: InternalHandlerBuilder = ({
6674
mwApi,
6775
internalState,
6876
) => {
69-
if (unsubscribeQueryResult.match(action)) {
77+
if (canTriggerUnsubscribe(action)) {
7078
const state = mwApi.getState()[reducerPath]
71-
const { queryCacheKey } = action.payload
79+
const { queryCacheKey } = unsubscribeQueryResult.match(action)
80+
? action.payload
81+
: action.meta.arg
7282

7383
handleUnsubscribe(
7484
queryCacheKey,

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,68 @@ describe('hooks tests', () => {
724724
expect(res.data!.amount).toBeGreaterThan(originalAmount)
725725
})
726726

727+
// See https://github.com/reduxjs/redux-toolkit/issues/4267 - Memory leak in useQuery rapid query arg changes
728+
test('Hook subscriptions are properly cleaned up when query is fulfilled/rejected', async () => {
729+
// This is imported already, but it seems to be causing issues with the test on certain matrixes
730+
function delay(ms: number) {
731+
return new Promise((resolve) => setTimeout(resolve, ms))
732+
}
733+
734+
const pokemonApi = createApi({
735+
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
736+
endpoints: (builder) => ({
737+
getTest: builder.query<string, number>({
738+
async queryFn() {
739+
await new Promise((resolve) => setTimeout(resolve, 1000));
740+
return { data: "data!" };
741+
},
742+
keepUnusedDataFor: 0,
743+
}),
744+
}),
745+
})
746+
747+
const storeRef = setupApiStore(pokemonApi, undefined, {
748+
withoutTestLifecycles: true,
749+
})
750+
751+
const checkNumQueries = (count: number) => {
752+
const cacheEntries = Object.keys((storeRef.store.getState()).api.queries)
753+
const queries = cacheEntries.length
754+
755+
expect(queries).toBe(count)
756+
}
757+
758+
let i = 0;
759+
760+
function User() {
761+
const [fetchTest, { isFetching, isUninitialized }] =
762+
pokemonApi.endpoints.getTest.useLazyQuery()
763+
764+
return (
765+
<div>
766+
<div data-testid="isUninitialized">{String(isUninitialized)}</div>
767+
<div data-testid="isFetching">{String(isFetching)}</div>
768+
<button data-testid="fetchButton" onClick={() => fetchTest(i++)}>
769+
fetchUser
770+
</button>
771+
</div>
772+
)
773+
}
774+
775+
render(<User />, { wrapper: storeRef.wrapper })
776+
fireEvent.click(screen.getByTestId('fetchButton'))
777+
fireEvent.click(screen.getByTestId('fetchButton'))
778+
fireEvent.click(screen.getByTestId('fetchButton'))
779+
checkNumQueries(3)
780+
781+
await act(async () => {
782+
await delay(1500)
783+
})
784+
785+
// There should only be one stored query once they have had time to resolve
786+
checkNumQueries( 1)
787+
})
788+
727789
// See https://github.com/reduxjs/redux-toolkit/issues/3182
728790
test('Hook subscriptions are properly cleaned up when changing skip back and forth', async () => {
729791
const pokemonApi = createApi({

packages/toolkit/tsup.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const buildTargets: BuildOptions[] = [
7171
{
7272
format: 'esm',
7373
name: 'legacy-esm',
74-
target: 'esnext',
74+
target: 'es2017',
7575
minify: false,
7676
env: '',
7777
},

0 commit comments

Comments
 (0)