@depup/reduxjs__toolkit 2.12.0-depup.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/changes.json +18 -0
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/redux-toolkit.development.cjs +2406 -0
- package/dist/cjs/redux-toolkit.development.cjs.map +1 -0
- package/dist/cjs/redux-toolkit.production.min.cjs +3 -0
- package/dist/cjs/redux-toolkit.production.min.cjs.map +1 -0
- package/dist/index.d.mts +2665 -0
- package/dist/index.d.ts +2665 -0
- package/dist/query/cjs/index.js +6 -0
- package/dist/query/cjs/rtk-query.development.cjs +3092 -0
- package/dist/query/cjs/rtk-query.development.cjs.map +1 -0
- package/dist/query/cjs/rtk-query.production.min.cjs +2 -0
- package/dist/query/cjs/rtk-query.production.min.cjs.map +1 -0
- package/dist/query/index.d.mts +3066 -0
- package/dist/query/index.d.ts +3066 -0
- package/dist/query/react/cjs/index.js +6 -0
- package/dist/query/react/cjs/rtk-query-react.development.cjs +748 -0
- package/dist/query/react/cjs/rtk-query-react.development.cjs.map +1 -0
- package/dist/query/react/cjs/rtk-query-react.production.min.cjs +2 -0
- package/dist/query/react/cjs/rtk-query-react.production.min.cjs.map +1 -0
- package/dist/query/react/index.d.mts +1009 -0
- package/dist/query/react/index.d.ts +1009 -0
- package/dist/query/react/rtk-query-react.browser.mjs +2 -0
- package/dist/query/react/rtk-query-react.browser.mjs.map +1 -0
- package/dist/query/react/rtk-query-react.legacy-esm.js +740 -0
- package/dist/query/react/rtk-query-react.legacy-esm.js.map +1 -0
- package/dist/query/react/rtk-query-react.modern.mjs +705 -0
- package/dist/query/react/rtk-query-react.modern.mjs.map +1 -0
- package/dist/query/rtk-query.browser.mjs +2 -0
- package/dist/query/rtk-query.browser.mjs.map +1 -0
- package/dist/query/rtk-query.legacy-esm.js +3117 -0
- package/dist/query/rtk-query.legacy-esm.js.map +1 -0
- package/dist/query/rtk-query.modern.mjs +3052 -0
- package/dist/query/rtk-query.modern.mjs.map +1 -0
- package/dist/react/cjs/index.js +6 -0
- package/dist/react/cjs/redux-toolkit-react.development.cjs +55 -0
- package/dist/react/cjs/redux-toolkit-react.development.cjs.map +1 -0
- package/dist/react/cjs/redux-toolkit-react.production.min.cjs +2 -0
- package/dist/react/cjs/redux-toolkit-react.production.min.cjs.map +1 -0
- package/dist/react/index.d.mts +22 -0
- package/dist/react/index.d.ts +22 -0
- package/dist/react/redux-toolkit-react.browser.mjs +2 -0
- package/dist/react/redux-toolkit-react.browser.mjs.map +1 -0
- package/dist/react/redux-toolkit-react.legacy-esm.js +47 -0
- package/dist/react/redux-toolkit-react.legacy-esm.js.map +1 -0
- package/dist/react/redux-toolkit-react.modern.mjs +28 -0
- package/dist/react/redux-toolkit-react.modern.mjs.map +1 -0
- package/dist/redux-toolkit.browser.mjs +3 -0
- package/dist/redux-toolkit.browser.mjs.map +1 -0
- package/dist/redux-toolkit.legacy-esm.js +2351 -0
- package/dist/redux-toolkit.legacy-esm.js.map +1 -0
- package/dist/redux-toolkit.modern.mjs +2330 -0
- package/dist/redux-toolkit.modern.mjs.map +1 -0
- package/dist/uncheckedindexed.ts +16 -0
- package/package.json +310 -0
- package/query/package.json +13 -0
- package/query/react/package.json +13 -0
- package/react/package.json +13 -0
- package/skills/build-modern-redux-apps/modern-redux/SKILL.md +304 -0
- package/skills/build-modern-redux-apps/modern-redux/references/store-lifetime.md +53 -0
- package/skills/build-modern-redux-apps/redux-dataflow/SKILL.md +264 -0
- package/skills/evolve-and-diagnose-redux-apps/debug-redux-toolkit-apps/SKILL.md +269 -0
- package/skills/evolve-and-diagnose-redux-apps/migrate-to-modern-redux/SKILL.md +226 -0
- package/skills/manage-server-data/adopt-rtk-query/SKILL.md +385 -0
- package/skills/manage-server-data/adopt-rtk-query/references/endpoint-lifecycle.md +36 -0
- package/skills/model-redux-state/build-slices-and-selectors/SKILL.md +364 -0
- package/skills/model-redux-state/build-slices-and-selectors/references/slice-patterns.md +59 -0
- package/skills/model-redux-state/design-state-ownership/SKILL.md +322 -0
- package/skills/model-redux-state/design-state-ownership/references/state-ownership.md +28 -0
- package/skills/orchestrate-side-effects/handle-side-effects/SKILL.md +271 -0
- package/skills/orchestrate-side-effects/handle-side-effects/references/listener-workflows.md +34 -0
- package/src/actionCreatorInvariantMiddleware.ts +34 -0
- package/src/autoBatchEnhancer.ts +146 -0
- package/src/combineSlices.ts +487 -0
- package/src/configureStore.ts +248 -0
- package/src/createAction.ts +324 -0
- package/src/createAsyncThunk.ts +791 -0
- package/src/createDraftSafeSelector.ts +30 -0
- package/src/createReducer.ts +217 -0
- package/src/createSlice.ts +1079 -0
- package/src/devtoolsExtension.ts +241 -0
- package/src/dynamicMiddleware/index.ts +93 -0
- package/src/dynamicMiddleware/react/index.ts +101 -0
- package/src/dynamicMiddleware/types.ts +82 -0
- package/src/entities/create_adapter.ts +47 -0
- package/src/entities/entity_state.ts +38 -0
- package/src/entities/index.ts +8 -0
- package/src/entities/models.ts +198 -0
- package/src/entities/sorted_state_adapter.ts +266 -0
- package/src/entities/state_adapter.ts +58 -0
- package/src/entities/state_selectors.ts +73 -0
- package/src/entities/unsorted_state_adapter.ts +204 -0
- package/src/entities/utils.ts +68 -0
- package/src/formatProdErrorMessage.ts +13 -0
- package/src/getDefaultEnhancers.ts +31 -0
- package/src/getDefaultMiddleware.ts +113 -0
- package/src/immerImports.ts +7 -0
- package/src/immutableStateInvariantMiddleware.ts +274 -0
- package/src/index.ts +213 -0
- package/src/listenerMiddleware/exceptions.ts +20 -0
- package/src/listenerMiddleware/index.ts +562 -0
- package/src/listenerMiddleware/task.ts +100 -0
- package/src/listenerMiddleware/types.ts +886 -0
- package/src/listenerMiddleware/utils.ts +30 -0
- package/src/mapBuilders.ts +298 -0
- package/src/matchers.ts +365 -0
- package/src/nanoid.ts +20 -0
- package/src/query/HandledError.ts +6 -0
- package/src/query/apiTypes.ts +120 -0
- package/src/query/baseQueryTypes.ts +101 -0
- package/src/query/core/apiState.ts +377 -0
- package/src/query/core/buildInitiate.ts +596 -0
- package/src/query/core/buildMiddleware/batchActions.ts +207 -0
- package/src/query/core/buildMiddleware/cacheCollection.ts +204 -0
- package/src/query/core/buildMiddleware/cacheLifecycle.ts +379 -0
- package/src/query/core/buildMiddleware/devMiddleware.ts +34 -0
- package/src/query/core/buildMiddleware/index.ts +162 -0
- package/src/query/core/buildMiddleware/invalidationByTags.ts +139 -0
- package/src/query/core/buildMiddleware/polling.ts +187 -0
- package/src/query/core/buildMiddleware/queryLifecycle.ts +508 -0
- package/src/query/core/buildMiddleware/types.ts +173 -0
- package/src/query/core/buildMiddleware/windowEventHandling.ts +64 -0
- package/src/query/core/buildSelectors.ts +413 -0
- package/src/query/core/buildSlice.ts +735 -0
- package/src/query/core/buildThunks.ts +1124 -0
- package/src/query/core/index.ts +58 -0
- package/src/query/core/module.ts +727 -0
- package/src/query/core/rtkImports.ts +24 -0
- package/src/query/core/setupListeners.ts +118 -0
- package/src/query/createApi.ts +506 -0
- package/src/query/defaultSerializeQueryArgs.ts +52 -0
- package/src/query/endpointDefinitions.ts +1717 -0
- package/src/query/fakeBaseQuery.ts +21 -0
- package/src/query/fetchBaseQuery.ts +385 -0
- package/src/query/index.ts +105 -0
- package/src/query/react/ApiProvider.tsx +69 -0
- package/src/query/react/buildHooks.ts +2297 -0
- package/src/query/react/constants.ts +2 -0
- package/src/query/react/index.ts +45 -0
- package/src/query/react/module.ts +275 -0
- package/src/query/react/namedHooks.ts +59 -0
- package/src/query/react/reactImports.ts +10 -0
- package/src/query/react/reactReduxImports.ts +1 -0
- package/src/query/react/rtkqImports.ts +8 -0
- package/src/query/react/useSerializedStableValue.ts +17 -0
- package/src/query/react/useShallowStableValue.ts +13 -0
- package/src/query/retry.ts +233 -0
- package/src/query/standardSchema.ts +35 -0
- package/src/query/tsHelpers.ts +48 -0
- package/src/query/utils/capitalize.ts +3 -0
- package/src/query/utils/copyWithStructuralSharing.ts +27 -0
- package/src/query/utils/countObjectKeys.ts +14 -0
- package/src/query/utils/filterMap.ts +27 -0
- package/src/query/utils/getCurrent.ts +6 -0
- package/src/query/utils/getOrInsert.ts +41 -0
- package/src/query/utils/immerImports.ts +9 -0
- package/src/query/utils/index.ts +11 -0
- package/src/query/utils/isAbsoluteUrl.ts +9 -0
- package/src/query/utils/isDocumentVisible.ts +12 -0
- package/src/query/utils/isNotNullish.ts +7 -0
- package/src/query/utils/isOnline.ts +12 -0
- package/src/query/utils/isValidUrl.ts +9 -0
- package/src/query/utils/joinUrls.ts +26 -0
- package/src/query/utils/signals.ts +33 -0
- package/src/react/index.ts +7 -0
- package/src/reduxImports.ts +8 -0
- package/src/reselectImports.ts +1 -0
- package/src/serializableStateInvariantMiddleware.ts +285 -0
- package/src/tsHelpers.ts +214 -0
- package/src/uncheckedindexed.ts +16 -0
- package/src/utils.ts +125 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: model-redux-state/build-slices-and-selectors
|
|
3
|
+
description: >
|
|
4
|
+
Use this when authoring or refactoring slices with createSlice, selectors,
|
|
5
|
+
create.asyncThunk, entity adapters, or lazy reducer injection. Covers
|
|
6
|
+
Immer-backed mutation syntax, slice selectors, getSelectors, injectInto,
|
|
7
|
+
withLazyLoadedSlices, and current RTK 2 slice patterns.
|
|
8
|
+
type: core
|
|
9
|
+
library: "@reduxjs/toolkit"
|
|
10
|
+
library_version: "2.11.2"
|
|
11
|
+
requires:
|
|
12
|
+
- model-redux-state/design-state-ownership
|
|
13
|
+
sources:
|
|
14
|
+
- "reduxjs/redux-toolkit:docs/api/createSlice.mdx"
|
|
15
|
+
- "reduxjs/redux-toolkit:docs/api/combineSlices.mdx"
|
|
16
|
+
- "reduxjs/redux-toolkit:docs/api/createEntityAdapter.mdx"
|
|
17
|
+
- "reduxjs/redux-toolkit:docs/usage/immer-reducers.md"
|
|
18
|
+
- "reduxjs/redux-toolkit:docs/usage/migrating-rtk-2.md"
|
|
19
|
+
- "reduxjs/redux:docs/style-guide/style-guide.md"
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Build Slices And Selectors
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// file: src/app/createAppSlice.ts
|
|
28
|
+
import { asyncThunkCreator, buildCreateSlice } from '@reduxjs/toolkit'
|
|
29
|
+
|
|
30
|
+
export const createAppSlice = buildCreateSlice({
|
|
31
|
+
creators: { asyncThunk: asyncThunkCreator },
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// file: src/features/posts/postsSlice.ts
|
|
35
|
+
import { createSelector } from '@reduxjs/toolkit'
|
|
36
|
+
import { createAppSlice } from '../../app/createAppSlice'
|
|
37
|
+
|
|
38
|
+
type PostsState = {
|
|
39
|
+
items: { id: string; title: string; published: boolean }[]
|
|
40
|
+
status: 'idle' | 'pending' | 'succeeded' | 'failed'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const initialState: PostsState = {
|
|
44
|
+
items: [],
|
|
45
|
+
status: 'idle',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const postsSlice = createAppSlice({
|
|
49
|
+
name: 'posts',
|
|
50
|
+
initialState,
|
|
51
|
+
reducers: (create) => ({
|
|
52
|
+
postAdded: create.reducer<{ id: string; title: string }>((state, action) => {
|
|
53
|
+
state.items.push({ ...action.payload, published: false })
|
|
54
|
+
}),
|
|
55
|
+
fetchPosts: create.asyncThunk(
|
|
56
|
+
async () => {
|
|
57
|
+
const response = await fetch('/api/posts')
|
|
58
|
+
return (await response.json()) as { id: string; title: string; published: boolean }[]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
pending: (state) => {
|
|
62
|
+
state.status = 'pending'
|
|
63
|
+
},
|
|
64
|
+
fulfilled: (state, action) => {
|
|
65
|
+
state.status = 'succeeded'
|
|
66
|
+
state.items = action.payload
|
|
67
|
+
},
|
|
68
|
+
rejected: (state) => {
|
|
69
|
+
state.status = 'failed'
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
),
|
|
73
|
+
}),
|
|
74
|
+
selectors: {
|
|
75
|
+
selectPosts: (state) => state.items,
|
|
76
|
+
selectPublishedPosts: createSelector(
|
|
77
|
+
[(state: PostsState) => state.items],
|
|
78
|
+
(items) => items.filter((post) => post.published),
|
|
79
|
+
),
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
export const { postAdded, fetchPosts } = postsSlice.actions
|
|
84
|
+
export const { selectPosts, selectPublishedPosts } = postsSlice.selectors
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Core Patterns
|
|
88
|
+
|
|
89
|
+
### Use mutating logic inside slice reducers
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { createSlice } from '@reduxjs/toolkit'
|
|
93
|
+
|
|
94
|
+
const todosSlice = createSlice({
|
|
95
|
+
name: 'todos',
|
|
96
|
+
initialState: [] as { id: string; text: string; done: boolean }[],
|
|
97
|
+
reducers: {
|
|
98
|
+
todoAdded(state, action: { payload: { id: string; text: string } }) {
|
|
99
|
+
state.push({ ...action.payload, done: false })
|
|
100
|
+
},
|
|
101
|
+
todoToggled(state, action: { payload: { id: string } }) {
|
|
102
|
+
const todo = state.find((item) => item.id === action.payload.id)
|
|
103
|
+
if (todo) {
|
|
104
|
+
todo.done = !todo.done
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Immer is the default inside `createSlice`; write the reducer logic directly instead of copying arrays and objects by hand.
|
|
112
|
+
|
|
113
|
+
### Define selectors in the slice when they belong to the slice
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { createSlice } from '@reduxjs/toolkit'
|
|
117
|
+
|
|
118
|
+
const counterSlice = createSlice({
|
|
119
|
+
name: 'counter',
|
|
120
|
+
initialState: { value: 0 },
|
|
121
|
+
reducers: {
|
|
122
|
+
increment(state) {
|
|
123
|
+
state.value += 1
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
selectors: {
|
|
127
|
+
selectValue: (state) => state.value,
|
|
128
|
+
selectIsPositive: (state) => state.value > 0,
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
const { selectValue, selectIsPositive } = counterSlice.selectors
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Slice selectors keep state-location knowledge next to the slice.
|
|
136
|
+
|
|
137
|
+
### Use `create.asyncThunk` when the async lifecycle belongs to the slice
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { asyncThunkCreator, buildCreateSlice } from '@reduxjs/toolkit'
|
|
141
|
+
|
|
142
|
+
const createAppSlice = buildCreateSlice({
|
|
143
|
+
creators: { asyncThunk: asyncThunkCreator },
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const usersSlice = createAppSlice({
|
|
147
|
+
name: 'users',
|
|
148
|
+
initialState: { items: [] as { id: string; name: string }[], status: 'idle' as 'idle' | 'pending' | 'failed' },
|
|
149
|
+
reducers: (create) => ({
|
|
150
|
+
fetchUsers: create.asyncThunk(
|
|
151
|
+
async () => {
|
|
152
|
+
const response = await fetch('/api/users')
|
|
153
|
+
return (await response.json()) as { id: string; name: string }[]
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
pending: (state) => {
|
|
157
|
+
state.status = 'pending'
|
|
158
|
+
},
|
|
159
|
+
fulfilled: (state, action) => {
|
|
160
|
+
state.status = 'idle'
|
|
161
|
+
state.items = action.payload
|
|
162
|
+
},
|
|
163
|
+
rejected: (state) => {
|
|
164
|
+
state.status = 'failed'
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
),
|
|
168
|
+
}),
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Use this when the async lifecycle handlers naturally live with the slice; otherwise regular `createAsyncThunk` is still fine.
|
|
173
|
+
|
|
174
|
+
### Use entity adapters and lazy injection for scalable slices
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import {
|
|
178
|
+
combineSlices,
|
|
179
|
+
createEntityAdapter,
|
|
180
|
+
createSlice,
|
|
181
|
+
} from '@reduxjs/toolkit'
|
|
182
|
+
|
|
183
|
+
type Book = { bookId: string; title: string }
|
|
184
|
+
|
|
185
|
+
const booksAdapter = createEntityAdapter<Book>({
|
|
186
|
+
selectId: (book) => book.bookId,
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
const booksSlice = createSlice({
|
|
190
|
+
name: 'books',
|
|
191
|
+
initialState: booksAdapter.getInitialState(),
|
|
192
|
+
reducers: {
|
|
193
|
+
booksReceived: booksAdapter.setAll,
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
export interface LazyLoadedSlices {}
|
|
198
|
+
|
|
199
|
+
export const rootReducer =
|
|
200
|
+
combineSlices().withLazyLoadedSlices<LazyLoadedSlices>()
|
|
201
|
+
|
|
202
|
+
declare module './rootReducer' {
|
|
203
|
+
export interface LazyLoadedSlices {}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const injectedBooksSlice = booksSlice.injectInto(rootReducer)
|
|
207
|
+
|
|
208
|
+
const selectors = booksAdapter.getSelectors(
|
|
209
|
+
(state: ReturnType<typeof rootReducer.selector.original>) =>
|
|
210
|
+
injectedBooksSlice.selectSlice(state),
|
|
211
|
+
)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Entity adapters standardize normalized collections, and `injectInto` lets a slice stay aware of its injected location.
|
|
215
|
+
|
|
216
|
+
## Common Mistakes
|
|
217
|
+
|
|
218
|
+
### CRITICAL Using mutating logic outside slice reducers
|
|
219
|
+
|
|
220
|
+
Wrong:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
type Todo = { id: string; text: string }
|
|
224
|
+
|
|
225
|
+
export function addTodo(todos: Todo[], todo: Todo) {
|
|
226
|
+
todos.push(todo)
|
|
227
|
+
return todos
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Correct:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
type Todo = { id: string; text: string }
|
|
235
|
+
|
|
236
|
+
const todosSlice = createSlice({
|
|
237
|
+
name: 'todos',
|
|
238
|
+
initialState: [] as Todo[],
|
|
239
|
+
reducers: {
|
|
240
|
+
todoAdded(state, action: { payload: Todo }) {
|
|
241
|
+
state.push(action.payload)
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
})
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Mutation syntax is only safe inside Immer-backed reducer contexts such as `createSlice` and `createReducer`.
|
|
248
|
+
|
|
249
|
+
Source: reduxjs/redux-toolkit:docs/usage/immer-reducers.md
|
|
250
|
+
|
|
251
|
+
### HIGH Writing hand-written switch reducers as the default
|
|
252
|
+
|
|
253
|
+
Wrong:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
export default function todosReducer(state = initialState, action: { type: string; payload?: Todo }) {
|
|
257
|
+
switch (action.type) {
|
|
258
|
+
case 'todos/todoAdded':
|
|
259
|
+
return state.concat(action.payload as Todo)
|
|
260
|
+
default:
|
|
261
|
+
return state
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Correct:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
const todosSlice = createSlice({
|
|
270
|
+
name: 'todos',
|
|
271
|
+
initialState,
|
|
272
|
+
reducers: {
|
|
273
|
+
todoAdded(state, action: { payload: Todo }) {
|
|
274
|
+
state.push(action.payload)
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
})
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Hand-written reducers are an escape hatch for proven bottlenecks, not the normal thing an agent should generate in RTK code.
|
|
281
|
+
|
|
282
|
+
Source: reduxjs/redux-toolkit:docs/usage/migrating-to-modern-redux.mdx
|
|
283
|
+
|
|
284
|
+
### HIGH Writing RTK 1.x object syntax for `extraReducers`
|
|
285
|
+
|
|
286
|
+
Wrong:
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
|
|
290
|
+
|
|
291
|
+
const initialState = { items: [] as { id: string; title: string }[] }
|
|
292
|
+
|
|
293
|
+
const fetchPosts = createAsyncThunk('posts/fetch', async () => {
|
|
294
|
+
const response = await fetch('/api/posts')
|
|
295
|
+
return (await response.json()) as { id: string; title: string }[]
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
const postsSlice = createSlice({
|
|
299
|
+
name: 'posts',
|
|
300
|
+
initialState,
|
|
301
|
+
reducers: {},
|
|
302
|
+
extraReducers: {
|
|
303
|
+
[fetchPosts.fulfilled.type]: (state, action) => {
|
|
304
|
+
state.items = action.payload
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
})
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Correct:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
|
|
314
|
+
|
|
315
|
+
const initialState = { items: [] as { id: string; title: string }[] }
|
|
316
|
+
|
|
317
|
+
const fetchPosts = createAsyncThunk('posts/fetch', async () => {
|
|
318
|
+
const response = await fetch('/api/posts')
|
|
319
|
+
return (await response.json()) as { id: string; title: string }[]
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
const postsSlice = createSlice({
|
|
323
|
+
name: 'posts',
|
|
324
|
+
initialState,
|
|
325
|
+
reducers: {},
|
|
326
|
+
extraReducers: (builder) => {
|
|
327
|
+
builder.addCase(fetchPosts.fulfilled, (state, action) => {
|
|
328
|
+
state.items = action.payload
|
|
329
|
+
})
|
|
330
|
+
},
|
|
331
|
+
})
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
RTK 2 removed the object form; agents trained on RTK 1.x still generate it.
|
|
335
|
+
|
|
336
|
+
Source: reduxjs/redux-toolkit:docs/usage/migrating-rtk-2.md
|
|
337
|
+
|
|
338
|
+
### HIGH Assuming `entity.id` exists for every collection
|
|
339
|
+
|
|
340
|
+
Wrong:
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
type Book = { bookId: string; title: string }
|
|
344
|
+
|
|
345
|
+
const booksAdapter = createEntityAdapter<Book>()
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Correct:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
type Book = { bookId: string; title: string }
|
|
352
|
+
|
|
353
|
+
const booksAdapter = createEntityAdapter<Book>({
|
|
354
|
+
selectId: (book) => book.bookId,
|
|
355
|
+
})
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Adapters default to `entity.id`; collections keyed by another field must provide `selectId`.
|
|
359
|
+
|
|
360
|
+
Source: reduxjs/redux-toolkit:docs/api/createEntityAdapter.mdx
|
|
361
|
+
|
|
362
|
+
## References
|
|
363
|
+
|
|
364
|
+
- [Slice selectors, async creators, and lazy injection details](references/slice-patterns.md)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Slice Patterns
|
|
2
|
+
|
|
3
|
+
## `getSelectors` for alternate mounting points
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
const counterSlice = createSlice({
|
|
7
|
+
name: 'counter',
|
|
8
|
+
initialState: { value: 0 },
|
|
9
|
+
reducers: {},
|
|
10
|
+
selectors: {
|
|
11
|
+
selectValue: (state) => state.value,
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export const { selectValue } = counterSlice.getSelectors(
|
|
16
|
+
(state: { customCounter: { value: number } }) => state.customCounter,
|
|
17
|
+
)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Use `getSelectors` when the slice is not mounted at its default `reducerPath`.
|
|
21
|
+
|
|
22
|
+
## `withLazyLoadedSlices` and `injectInto`
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { combineSlices, createSlice, type WithSlice } from '@reduxjs/toolkit'
|
|
26
|
+
|
|
27
|
+
const staticSlice = createSlice({
|
|
28
|
+
name: 'static',
|
|
29
|
+
initialState: { ready: true },
|
|
30
|
+
reducers: {},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export interface LazyLoadedSlices {}
|
|
34
|
+
|
|
35
|
+
export const rootReducer =
|
|
36
|
+
combineSlices(staticSlice).withLazyLoadedSlices<LazyLoadedSlices>()
|
|
37
|
+
|
|
38
|
+
const lazySlice = createSlice({
|
|
39
|
+
name: 'lazy',
|
|
40
|
+
initialState: { value: 0 },
|
|
41
|
+
reducers: {
|
|
42
|
+
increment(state) {
|
|
43
|
+
state.value += 1
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
declare module '../rootReducer' {
|
|
49
|
+
export interface LazyLoadedSlices extends WithSlice<typeof lazySlice> {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const injectedLazySlice = lazySlice.injectInto(rootReducer)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This keeps RootState types aware of reducers that will be injected later.
|
|
56
|
+
|
|
57
|
+
## Selector caveat
|
|
58
|
+
|
|
59
|
+
If a selector depends on caller-specific arguments and must be memoized per caller, prefer a selector factory outside `createSlice.selectors`. `createSlice.selectors` gives you one selector instance, not a selector factory.
|