Skip to content

Commit 4a4a666

Browse files
committed
Merge branch 'master' of https://github.com/reduxjs/redux-toolkit into configs
2 parents b624627 + 0246f78 commit 4a4a666

23 files changed

+31455
-30713
lines changed

.codesandbox/ci.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"sandboxes": [
33
"vanilla",
44
"vanilla-ts",
5-
"github/reduxjs/rtk-github-issues-example",
65
"/examples/query/react/basic",
76
"/examples/query/react/advanced",
87
"/examples/action-listener/counter",

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ jobs:
106106
fail-fast: false
107107
matrix:
108108
node: ['20.x']
109-
ts: ['4.7', '4.8', '4.9', '5.0', '5.1', '5.2', '5.3']
109+
ts: ['4.7', '4.8', '4.9', '5.0', '5.1', '5.2', '5.3', '5.4']
110110
steps:
111111
- name: Checkout repo
112112
uses: actions/checkout@v4

examples/query/react/graphql-codegen/package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@
4242
"ts-node": "^10.0.0",
4343
"typescript": "4.1.3"
4444
},
45-
"resolutions": {
46-
"@typescript-eslint/parser": "4.4.1"
47-
},
4845
"scripts": {
4946
"develop": "cross-env CHOKIDAR_USEPOLLING=true yarn start",
5047
"start": "concurrently --restart-tries 20 'npm:watch-*'",

package.json

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,18 @@
2424
"packages/configs/*"
2525
],
2626
"devDependencies": {
27+
"@babel/code-frame": "^7.24.2",
28+
"@babel/core": "^7.24.3",
29+
"@babel/generator": "^7.24.1",
30+
"@babel/helper-compilation-targets": "^7.23.6",
31+
"@babel/traverse": "^7.24.1",
32+
"@babel/types": "^7.24.0",
2733
"@reduxjs/eslint-config": "workspace:^",
2834
"@reduxjs/prettier-config": "workspace:^",
2935
"@reduxjs/tsconfig": "workspace:^",
3036
"@reduxjs/vitest-config": "workspace:^",
37+
"@types/react": "^18.2.77",
38+
"@types/react-dom": "^18.2.25",
3139
"@typescript-eslint/eslint-plugin": "^7.8.0",
3240
"@typescript-eslint/parser": "^7.8.0",
3341
"eslint": "^7.25.0",
@@ -41,36 +49,17 @@
4149
"eslint-plugin-react-hooks": "^4.2.0",
4250
"netlify-plugin-cache": "^1.0.3",
4351
"prettier": "^3.2.5",
52+
"react": "^18.2.0",
53+
"react-dom": "^18.2.0",
4454
"react-redux": "^9.1.0",
4555
"release-it": "^14.12.5",
4656
"serve": "^14.2.0",
4757
"ts-node": "^10.9.2",
48-
"typescript": "^5.2.2"
58+
"typescript": "^5.4.3"
4959
},
5060
"resolutions": {
51-
"@babel/core": "7.19.3",
52-
"@babel/code-frame": "7.18.6",
53-
"@babel/generator": "7.19.3",
54-
"@babel/helper-compilation-targets": "7.19.3",
55-
"@babel/traverse": "7.19.3",
56-
"@babel/types": "7.19.3",
5761
"esbuild": "0.19.7",
5862
"jest-snapshot": "29.3.1",
59-
"react": "npm:^18.2.0",
60-
"react-dom": "npm:18.2.0",
61-
"resolve": "1.22.1",
62-
"@types/react": "npm:^18.0.12",
63-
"@types/react-dom": "npm:18.0.5",
64-
"@types/inquirer": "npm:8.2.1",
65-
"website/react": "npm:17.0.2",
66-
"website/react-dom": "npm:17.0.2",
67-
"website/@types/react-dom": "npm:17.0.11",
68-
"website/@types/react": "npm:17.0.11",
69-
"docs/react": "npm:17.0.2",
70-
"docs/react-dom": "npm:17.0.2",
71-
"docs/@types/react-dom": "npm:17.0.11",
72-
"docs/@types/react": "npm:17.0.11",
73-
"type-fest": "2.19.0",
7463
"[email protected]": "patch:console-testing-library@npm%3A0.6.1#./.yarn/patches/console-testing-library-npm-0.6.1-4d9957d402.patch"
7564
},
7665
"scripts": {

packages/toolkit/.size-limit.cjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ const esmSuffixes = ['modern.mjs' /*, 'browser.mjs', 'legacy-esm.js'*/]
55
const cjsSuffixes = [/*'development.cjs',*/ 'production.min.cjs']
66

77
/**
8-
* @param {string} suffix
9-
* @param {boolean} cjs
10-
*/
8+
* @param {string} suffix
9+
* @param {boolean} cjs
10+
*/
1111
function withRtkPath(suffix, cjs = false) {
1212
/**
1313
* @param {string} name

packages/toolkit/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@reduxjs/toolkit",
3-
"version": "2.2.3",
3+
"version": "2.2.4",
44
"description": "The official, opinionated, batteries-included toolset for efficient Redux development",
55
"author": "Mark Erikson <[email protected]>",
66
"license": "MIT",
@@ -89,7 +89,7 @@
8989
"tslib": "^1.10.0",
9090
"tsup": "^7.2.0",
9191
"tsx": "^3.12.2",
92-
"typescript": "^5.3.3",
92+
"typescript": "^5.4.5",
9393
"vitest": "^1.5.2",
9494
"yargs": "^15.3.1"
9595
},

packages/toolkit/src/createAsyncThunk.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export const miniSerializeError = (value: any): SerializedError => {
115115

116116
export interface AsyncThunkConfig {
117117
state?: unknown
118-
dispatch?: Dispatch
118+
dispatch?: ThunkDispatch<unknown, unknown, UnknownAction>
119119
extra?: unknown
120120
rejectValue?: unknown
121121
serializedErrorType?: unknown
@@ -239,7 +239,7 @@ export type AsyncThunkAction<
239239
ThunkArg,
240240
ThunkApiConfig extends AsyncThunkConfig,
241241
> = (
242-
dispatch: GetDispatch<ThunkApiConfig>,
242+
dispatch: NonNullable<GetDispatch<ThunkApiConfig>>,
243243
getState: () => GetState<ThunkApiConfig>,
244244
extra: GetExtra<ThunkApiConfig>,
245245
) => SafePromise<
@@ -576,7 +576,7 @@ export const createAsyncThunk = /* @__PURE__ */ (() => {
576576

577577
function actionCreator(
578578
arg: ThunkArg,
579-
): AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> {
579+
): AsyncThunkAction<Returned, ThunkArg, Required<ThunkApiConfig>> {
580580
return (dispatch, getState, extra) => {
581581
const requestId = options?.idGenerator
582582
? options.idGenerator(arg)

packages/toolkit/src/createSlice.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -433,15 +433,7 @@ export interface ReducerCreators<State> {
433433
* @public
434434
*/
435435
export type SliceCaseReducers<State> =
436-
| Record<
437-
string,
438-
| CaseReducerDefinition<State, PayloadAction<any>>
439-
| CaseReducerWithPrepareDefinition<
440-
State,
441-
PayloadAction<any, string, any, any>
442-
>
443-
| AsyncThunkSliceReducerDefinition<State, any, any, any>
444-
>
436+
| Record<string, ReducerDefinition>
445437
| Record<
446438
string,
447439
| CaseReducer<State, PayloadAction<any>>
@@ -699,7 +691,7 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) {
699691
} else {
700692
handleNormalReducerDefinition<State>(
701693
reducerDetails,
702-
reducerDefinition,
694+
reducerDefinition as any,
703695
contextMethods,
704696
)
705697
}

packages/toolkit/src/entities/sorted_state_adapter.ts

Lines changed: 114 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { current } from 'immer'
12
import type {
23
Comparer,
34
DraftableEntityState,
@@ -10,13 +11,48 @@ import { createStateOperator } from './state_adapter'
1011
import { createUnsortedStateAdapter } from './unsorted_state_adapter'
1112
import {
1213
ensureEntitiesArray,
14+
getCurrent,
1315
selectIdValue,
1416
splitAddedUpdatedEntities,
1517
} from './utils'
1618

19+
// Borrowed from Replay
20+
export function findInsertIndex<T>(
21+
sortedItems: T[],
22+
item: T,
23+
comparisonFunction: Comparer<T>,
24+
): number {
25+
let lowIndex = 0
26+
let highIndex = sortedItems.length
27+
while (lowIndex < highIndex) {
28+
const middleIndex = (lowIndex + highIndex) >>> 1
29+
const currentItem = sortedItems[middleIndex]
30+
const res = comparisonFunction(item, currentItem)
31+
if (res >= 0) {
32+
lowIndex = middleIndex + 1
33+
} else {
34+
highIndex = middleIndex
35+
}
36+
}
37+
38+
return lowIndex
39+
}
40+
41+
export function insert<T>(
42+
sortedItems: T[],
43+
item: T,
44+
comparisonFunction: Comparer<T>,
45+
): T[] {
46+
const insertAtIndex = findInsertIndex(sortedItems, item, comparisonFunction)
47+
48+
sortedItems.splice(insertAtIndex, 0, item)
49+
50+
return sortedItems
51+
}
52+
1753
export function createSortedStateAdapter<T, Id extends EntityId>(
1854
selectId: IdSelector<T, Id>,
19-
sort: Comparer<T>,
55+
comparer: Comparer<T>,
2056
): EntityStateAdapter<T, Id> {
2157
type R = DraftableEntityState<T, Id>
2258

@@ -30,15 +66,20 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
3066
function addManyMutably(
3167
newEntities: readonly T[] | Record<Id, T>,
3268
state: R,
69+
existingIds?: Id[],
3370
): void {
3471
newEntities = ensureEntitiesArray(newEntities)
3572

73+
const existingKeys = new Set<Id>(
74+
existingIds ?? (current(state.ids) as Id[]),
75+
)
76+
3677
const models = newEntities.filter(
37-
(model) => !(selectIdValue(model, selectId) in state.entities),
78+
(model) => !existingKeys.has(selectIdValue(model, selectId)),
3879
)
3980

4081
if (models.length !== 0) {
41-
merge(models, state)
82+
mergeFunction(state, models)
4283
}
4384
}
4485

@@ -52,7 +93,10 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
5293
): void {
5394
newEntities = ensureEntitiesArray(newEntities)
5495
if (newEntities.length !== 0) {
55-
merge(newEntities, state)
96+
for (const item of newEntities) {
97+
delete (state.entities as Record<Id, T>)[selectId(item)]
98+
}
99+
mergeFunction(state, newEntities)
56100
}
57101
}
58102

@@ -64,7 +108,7 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
64108
state.entities = {} as Record<Id, T>
65109
state.ids = []
66110

67-
addManyMutably(newEntities, state)
111+
addManyMutably(newEntities, state, [])
68112
}
69113

70114
function updateOneMutably(update: Update<T, Id>, state: R): void {
@@ -76,6 +120,7 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
76120
state: R,
77121
): void {
78122
let appliedUpdates = false
123+
let replacedIds = false
79124

80125
for (const update of updates) {
81126
const entity: T | undefined = (state.entities as Record<Id, T>)[update.id]
@@ -87,14 +132,20 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
87132

88133
Object.assign(entity, update.changes)
89134
const newId = selectId(entity)
135+
90136
if (update.id !== newId) {
137+
// We do support the case where updates can change an item's ID.
138+
// This makes things trickier - go ahead and swap the IDs in state now.
139+
replacedIds = true
91140
delete (state.entities as Record<Id, T>)[update.id]
141+
const oldIndex = (state.ids as Id[]).indexOf(update.id)
142+
state.ids[oldIndex] = newId
92143
;(state.entities as Record<Id, T>)[newId] = entity
93144
}
94145
}
95146

96147
if (appliedUpdates) {
97-
resortEntities(state)
148+
mergeFunction(state, [], appliedUpdates, replacedIds)
98149
}
99150
}
100151

@@ -106,14 +157,18 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
106157
newEntities: readonly T[] | Record<Id, T>,
107158
state: R,
108159
): void {
109-
const [added, updated] = splitAddedUpdatedEntities<T, Id>(
160+
const [added, updated, existingIdsArray] = splitAddedUpdatedEntities<T, Id>(
110161
newEntities,
111162
selectId,
112163
state,
113164
)
114165

115-
updateManyMutably(updated, state)
116-
addManyMutably(added, state)
166+
if (updated.length) {
167+
updateManyMutably(updated, state)
168+
}
169+
if (added.length) {
170+
addManyMutably(added, state, existingIdsArray)
171+
}
117172
}
118173

119174
function areArraysEqual(a: readonly unknown[], b: readonly unknown[]) {
@@ -130,27 +185,65 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
130185
return true
131186
}
132187

133-
function merge(models: readonly T[], state: R): void {
188+
type MergeFunction = (
189+
state: R,
190+
addedItems: readonly T[],
191+
appliedUpdates?: boolean,
192+
replacedIds?: boolean,
193+
) => void
194+
195+
const mergeInsertion: MergeFunction = (
196+
state,
197+
addedItems,
198+
appliedUpdates,
199+
replacedIds,
200+
) => {
201+
const currentEntities = getCurrent(state.entities) as Record<Id, T>
202+
const currentIds = getCurrent(state.ids) as Id[]
203+
204+
const stateEntities = state.entities as Record<Id, T>
205+
206+
let ids = currentIds
207+
if (replacedIds) {
208+
ids = Array.from(new Set(currentIds))
209+
}
210+
211+
let sortedEntities: T[] = []
212+
for (const id of ids) {
213+
const entity = currentEntities[id]
214+
if (entity) {
215+
sortedEntities.push(entity)
216+
}
217+
}
218+
const wasPreviouslyEmpty = sortedEntities.length === 0
219+
134220
// Insert/overwrite all new/updated
135-
models.forEach((model) => {
136-
;(state.entities as Record<Id, T>)[selectId(model)] = model
137-
})
221+
for (const item of addedItems) {
222+
stateEntities[selectId(item)] = item
138223

139-
resortEntities(state)
140-
}
224+
if (!wasPreviouslyEmpty) {
225+
// Binary search insertion generally requires fewer comparisons
226+
insert(sortedEntities, item, comparer)
227+
}
228+
}
141229

142-
function resortEntities(state: R) {
143-
const allEntities = Object.values(state.entities) as T[]
144-
allEntities.sort(sort)
230+
if (wasPreviouslyEmpty) {
231+
// All we have is the incoming values, sort them
232+
sortedEntities = addedItems.slice().sort(comparer)
233+
} else if (appliedUpdates) {
234+
// We should have a _mostly_-sorted array already
235+
sortedEntities.sort(comparer)
236+
}
145237

146-
const newSortedIds = allEntities.map(selectId)
147-
const { ids } = state
238+
const newSortedIds = sortedEntities.map(selectId)
148239

149-
if (!areArraysEqual(ids, newSortedIds)) {
240+
if (!areArraysEqual(currentIds, newSortedIds)) {
150241
state.ids = newSortedIds
151242
}
152243
}
153244

245+
const mergeFunction: MergeFunction = mergeInsertion
246+
154247
return {
155248
removeOne,
156249
removeMany,

0 commit comments

Comments
 (0)