Skip to content

Commit 78b7033

Browse files
committed
write toString alternatives section
1 parent 7b10101 commit 78b7033

File tree

1 file changed

+71
-3
lines changed

1 file changed

+71
-3
lines changed

docs/migrations/1.x-to-2.x.md

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ We have also removed the `getType` export, which was used to extract a type stri
271271

272272
Previously, custom versions of React Redux's hooks (`useSelector`, `useDispatch`, and `useStore`) could be passed separately to `reactHooksModule`, usually to enable using a different context to the default `ReactReduxContext`.
273273

274-
The react hooks module requires all three of these hooks to be provided, and it became an easy mistake to only pass `useSelector` and `useDispatch`, without `useStore`.
274+
In practicality, the react hooks module needs all three of these hooks to be provided, and it became an easy mistake to only pass `useSelector` and `useDispatch`, without `useStore`.
275275

276276
The module has now moved all three of these under the same configuration key, and will check that all three are provided if the key is present.
277277

@@ -478,8 +478,8 @@ We've _wanted_ to include a way to define thunks directly inside of `createSlice
478478

479479
We've settled on these compromises:
480480

481-
- You can declare thunks inside of createSlice.reducers, by using a "creator callback" syntax for the reducers field that is similar to the build callback syntax in RTK Query's createApi (using typed functions to create fields in an object). Doing this does look a bit different than the existing "object" syntax for the reducers field, but is still fairly similar.
482-
- You can customize some of the types for thunks inside of createSlice, but you cannot customize the state or dispatch types. If those are needed, you can manually do an as cast, like getState() as RootState.
481+
- You can declare thunks inside of `createSlice.reducers`, by using a "creator callback" syntax for the `reducers` field that is similar to the `build` callback syntax in RTK Query's `createApi` (using typed functions to create fields in an object). Doing this does look a bit different than the existing "object" syntax for the `reducers` field, but is still fairly similar.
482+
- You can customize _some_ of the types for thunks inside of `createSlice`, but you _cannot_ customize the `state` or `dispatch` types. If those are needed, you can manually do an `as` cast, like `getState() as RootState`.
483483
- In order to create async thunks with `createSlice`, you specifically need to [set up a version that uses `createAsyncThunk`](../api/createSlice#createasyncthunk).
484484

485485
In practice, we hope these are reasonable tradeoffs. Creating thunks inside of `createSlice` has been widely asked for, so we think it's an API that will see usage. If the TS customization options are a limitation, you can still declare thunks outside of `createSlice` as always, and most async thunks don't need `dispatch` or `getState` - they just fetch data and return. And finally, setting up a custom `createSlice` allows you to opt into `createAsyncThunk` being included in your bundle size (though it may already be included if used directly or as part of RTK Query - in either of these cases there's no _additional_ bundle size).
@@ -628,8 +628,76 @@ We've updated RTK to depend on the final Immer 10.0 release.
628628

629629
## Recommendations
630630

631+
Based on changes in 2.0 and previous versions, there have been some shifts in thinking that are good to know about, if non-essential.
632+
631633
### Alternatives to `actionCreator.toString()`
632634

635+
As part of RTK's original API, action creators made with `createAction` have a custom `toString()` override that returns the action type.
636+
637+
This was primarily useful for the ([now removed](#object-syntax-for-createsliceextrareducers-and-createreducer-removed)) object syntax for `createReducer`:
638+
639+
```ts
640+
const todoAdded = createAction<Todo>('todos/todoAdded')
641+
642+
createReducer(initialState, {
643+
[todoAdded]: (state, action) => {}, // toString called here, 'todos/todoAdded'
644+
})
645+
```
646+
647+
While this was convenient (and other libraries in the Redux ecosystem such as `redux-saga` and `redux-observable` have supported this to various capacities), it didn't play well with Typescript and was generally a bit too "magic".
648+
649+
```ts
650+
const test = todoAdded.toString()
651+
// ^? typed as string, rather than specific action type
652+
```
653+
654+
Over time, the action creator also gained a static `type` property and `match` method which were more explicit and worked better with Typescript.
655+
656+
```ts
657+
const test = todoAdded.type
658+
// ^? 'todos/todoAdded'
659+
660+
// acts as a type predicate
661+
if (todoAdded.match(unknownAction)) {
662+
unknownAction.payload
663+
// ^? now typed as PayloadAction<Todo>
664+
}
665+
```
666+
667+
For compatibility, this override is still in place, but we encourage considering using either of the static properties for more understandable code.
668+
669+
For example, with `redux-saga`:
670+
671+
```ts
672+
// before (still works)
673+
yield takeEvery(todoAdded, saga)
674+
675+
// consider
676+
yield takeEvery(todoAdded.match, saga)
677+
// or
678+
yield takeEvery(todoAdded.type, saga)
679+
```
680+
681+
With `redux-observable`:
682+
683+
```ts
684+
// before (works in runtime, will not filter types properly)
685+
const epic = (action$: Observable<Action>) =>
686+
action$.pipe(
687+
ofType(todoAdded),
688+
map((action) => action)
689+
// ^? still Action<any>
690+
)
691+
692+
// consider (better type filtering)
693+
const epic = (action$: Observable<Action>) =>
694+
action$.pipe(
695+
filter(todoAdded.match),
696+
map((action) => action)
697+
// ^? now PayloadAction<Todo>
698+
)
699+
```
700+
633701
## Future plans
634702

635703
### Custom slice reducer creators

0 commit comments

Comments
 (0)