Skip to content

Commit a84abbd

Browse files
committed
expand upon creator docs further
1 parent 5e256a6 commit a84abbd

File tree

1 file changed

+114
-11
lines changed

1 file changed

+114
-11
lines changed

docs/usage/custom-slice-creators.mdx

Lines changed: 114 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,8 @@ The same as [`addCase`](../api/createReducer#builderaddcase) for `createReducer`
445445
```ts no-transpile
446446
const action = createAction(type)
447447
context.addCase(action, reducer)
448+
// or
449+
context.addCase(type, reducer)
448450
```
449451

450452
#### `addMatcher`
@@ -465,6 +467,12 @@ const action = createAction(type)
465467
context.exposeAction(action)
466468
```
467469

470+
:::caution
471+
472+
`exposeAction` should only be called once (at maximum) within a `handle` callback - the same applies to `exposeCaseReducer`.
473+
474+
:::
475+
468476
#### `exposeCaseReducer`
469477

470478
Attaches a value to `slice.caseReducers[reducerName]`.
@@ -478,12 +486,12 @@ context.exposeCaseReducer(reducer)
478486
Returns the initial state value for the slice. If a lazy state initializer has been provided, it will be called and a fresh value returned.
479487

480488
```ts no-transpile
481-
const resetAction = createAction(type)
489+
const resetAction = createAction(type + '/reset')
482490
const resetReducer = () => context.getInitialState()
483491
context
484492
.addCase(resetAction, resetReducer)
485-
.exposeAction(resetAction)
486-
.exposeCaseReducer(resetReducer)
493+
.exposeAction({ reset: resetAction })
494+
.exposeCaseReducer({ reset: resetReducer })
487495
```
488496

489497
#### `selectSlice`
@@ -503,7 +511,7 @@ const aThunk =
503511

504512
The Typescript system for custom slice creators uses a "creator registry" system similar to the module system for [RTK Query](/rtk-query/usage/customizing-create-api#creating-your-own-module).
505513

506-
Creators are registered by using module augmentation to add a new key (their unique `type`) to the `SliceReducerCreators` interface. The interface receives three type parameters (`State`, `CaseReducers` and `Name`), and each entry should use the `ReducerCreatorEntry` type utility.
514+
Creators are registered by using module augmentation to add a new key (their unique `type`) to the `SliceReducerCreators` interface. Each entry should use the `ReducerCreatorEntry` type utility.
507515

508516
```ts no-transpile
509517
const reducerCreatorType = Symbol('reducerCreatorType')
@@ -535,9 +543,9 @@ The `ReducerCreatorEntry<Create, Exposes>` utility has two type parameters:
535543

536544
The signature of the `create` method of the creator definition.
537545

538-
:::caution `CaseReducers` and `Name`
546+
:::caution `CaseReducers`
539547

540-
Your `Create` type should not depend on the `CaseReducers` and `Name` type parameters, as these will not yet exist when the creator is being called.
548+
Your `Create` type should not depend on the `CaseReducers` type parameter, as these will not yet exist when the creator is being called.
541549

542550
:::
543551

@@ -580,6 +588,39 @@ const batchedCreator: ReducerCreator<typeof batchedCreatorType> = {
580588

581589
The second argument to the `ReducerCreators` type is a map from creator names to types, which you should supply if you're expecting to use any custom creators (anything other than `reducer` and `preparedReducer`) within your own creator. For example, `ReducerCreators<State, { asyncThunk: typeof asyncThunkCreator.type }>` would allow you to call `this.asyncThunk`.
582590

591+
Alternatively, you can import the other creator's definition and use it directly.
592+
593+
```ts no-transpile
594+
import { preparedReducerCreator } from '@reduxjs/toolkit'
595+
596+
const batchedCreatorType = Symbol('batchedCreatorType')
597+
598+
declare module '@reduxjs/toolkit' {
599+
export interface SliceReducerCreators<
600+
State,
601+
CaseReducers extends CreatorCaseReducers<State>,
602+
Name extends string,
603+
ReducerPath extends string,
604+
> {
605+
[batchedCreatorType]: ReducerCreatorEntry<
606+
<Payload>(
607+
reducer: CaseReducer<State, PayloadAction<Payload>>,
608+
) => PreparedCaseReducerDefinition<
609+
State,
610+
(payload: Payload) => { payload: Payload; meta: unknown }
611+
>
612+
>
613+
}
614+
}
615+
616+
const batchedCreator: ReducerCreator<typeof batchedCreatorType> = {
617+
type: batchedCreatorType,
618+
create(reducer) {
619+
return preparedReducerCreator.create(prepareAutoBatched(), reducer)
620+
},
621+
}
622+
```
623+
583624
:::
584625

585626
:::note Ensuring compatible state
@@ -653,6 +694,8 @@ In order to ensure that the definitions are correctly filtered to only include t
653694
}
654695
```
655696

697+
To relate back to the context methods, it should describe what you will pass to `context.exposeAction` from a handler.
698+
656699
For example, with (a simplified version of) the `asyncThunk` creator:
657700

658701
```ts no-transpile
@@ -691,6 +734,8 @@ declare module '@reduxjs/toolkit' {
691734

692735
Similar to `actions`, except for `slice.caseReducers`.
693736

737+
It describes what you will pass to `context.exposeCaseReducer` from a handler.
738+
694739
For example, with the `preparedReducer` creator:
695740

696741
```ts no-transpile
@@ -956,6 +1001,8 @@ const paginationCreator: ReducerCreator<typeof paginationCreatorType> = {
9561001
type: paginationCreatorType,
9571002
create() {
9581003
return {
1004+
// calling `this.reducer` assumes we'll be calling the creator as `create.paginationMethods()`
1005+
// if we don't want this assumption, we could use `reducerCreator.create` instead
9591006
prevPage: this.reducer((state: PaginationState) => {
9601007
state.page--
9611008
}),
@@ -1011,7 +1058,7 @@ declare module '@reduxjs/toolkit' {
10111058
ReducerPath extends string,
10121059
> {
10131060
[historyCreatorType]: ReducerCreatorEntry<
1014-
// make sure the creator is only called when state is compatibleState extends HistoryState<unknown>
1061+
// make sure the creator is only called when state is compatible
10151062
State extends HistoryState<any>
10161063
? (this: ReducerCreators<State>) => {
10171064
undo: CaseReducerDefinition<State, PayloadAction>
@@ -1063,18 +1110,22 @@ const historyCreator: ReducerCreator<typeof historyCreatorType> = {
10631110
state.past.push(historyEntry)
10641111
}
10651112
}),
1113+
// highlight-start
1114+
// here we're creating a reducer definition that our `handle` method will be called with
10661115
reset: {
10671116
_reducerDefinitionType: historyCreatorType,
10681117
type: 'reset',
10691118
},
1119+
// highlight-end
10701120
}
10711121
},
10721122
handle(details, definition, context) {
10731123
if (definition.type !== 'reset') {
10741124
throw new Error('Unrecognised definition type: ' + definition.type)
10751125
}
1076-
// use the normal reducer creator to create a case reducer and action creator
10771126
const resetReducer = () => context.getInitialState()
1127+
// you can call other creators' `handle` methods if needed
1128+
// here we're reusing `reducerCreator` to get the expected behaviour of making an action creator for our reducer
10781129
reducerCreator.handle(details, reducerCreator.create(resetReducer), context)
10791130
},
10801131
}
@@ -1151,7 +1202,7 @@ const undoableCreator: ReducerCreator<typeof undoableCreatorType> = {
11511202
reducer: CaseReducer<any, A>,
11521203
): CaseReducer<HistoryState<any>, A> {
11531204
return (state, action) => {
1154-
const [nextState, redoPatch, undoPatch] = produceWithPatches(
1205+
const [nextState, redoPatches, undoPatches] = produceWithPatches(
11551206
state,
11561207
(draft) => {
11571208
const result = reducer(draft.present, action)
@@ -1165,8 +1216,8 @@ const undoableCreator: ReducerCreator<typeof undoableCreatorType> = {
11651216
if (undoable) {
11661217
finalState = createNextState(finalState, (draft) => {
11671218
draft.past.push({
1168-
undo: undoPatch,
1169-
redo: redoPatch,
1219+
undo: undoPatches,
1220+
redo: redoPatches,
11701221
})
11711222
draft.future = []
11721223
})
@@ -1221,3 +1272,55 @@ const postSliceWithHistory = createAppSlice({
12211272
const { undo, redo, reset, updateTitle, togglePinned } =
12221273
postSliceWithHistory.actions
12231274
```
1275+
1276+
:::tip `history-adapter`
1277+
1278+
This example is a somewhat simplified version of the [`history-adapter`](https://www.npmjs.com/package/history-adapter) package, which provides a `createHistoryAdapter` utility that can be used to add undo/redo functionality to a slice.
1279+
1280+
```ts no-transpile
1281+
import {
1282+
createHistoryAdapter,
1283+
historyMethodsCreator,
1284+
undoableCreatorsCreator,
1285+
} from 'history-adapter/redux'
1286+
1287+
const createAppSlice = buildCreateSlice({
1288+
creators: {
1289+
historyMethods: historyMethodsCreator,
1290+
undoableCreators: undoableCreatorsCreator,
1291+
},
1292+
})
1293+
1294+
const postHistoryAdapter = createHistoryAdapter<Post>({ limit: 5 })
1295+
1296+
const postSliceWithHistory = createAppSlice({
1297+
name: 'post',
1298+
initialState: postHistoryAdapter.getInitialState({
1299+
title: '',
1300+
pinned: false,
1301+
}),
1302+
reducers: (create) => {
1303+
const createUndoable = create.undoableCreators(postHistoryAdapter)
1304+
return {
1305+
...create.historyMethods(postHistoryAdapter),
1306+
updateTitle: createUndoable.preparedReducer(
1307+
postHistoryAdapter.withPayload<string>(),
1308+
(state, action) => {
1309+
state.title = action.payload
1310+
},
1311+
),
1312+
togglePinned: createUndoable.preparedReducer(
1313+
postHistoryAdapter.withoutPayload(),
1314+
(state, action) => {
1315+
state.pinned = !state.pinned
1316+
},
1317+
),
1318+
}
1319+
},
1320+
})
1321+
1322+
const { undo, redo, reset, updateTitle, togglePinned } =
1323+
postSliceWithHistory.actions
1324+
```
1325+
1326+
:::

0 commit comments

Comments
 (0)