Skip to content

Commit 61627cd

Browse files
committed
Edit new TS usage docs
1 parent 2e9be9c commit 61627cd

File tree

1 file changed

+78
-30
lines changed

1 file changed

+78
-30
lines changed

docs/usage/usage-with-typescript.md

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Using [configureStore](../api/configureStore.md) should not need any additional
2222
The easiest way of getting the `State` type is to define the root reducer in advance and extract its `ReturnType`.
2323
It is recommend to give the type a different name like `RootState` to prevent confusion, as the type name `State` is usually overused.
2424

25-
```typescript
25+
```typescript {3}
2626
import { combineReducers } from '@reduxjs/toolkit'
2727
const rootReducer = combineReducers({})
2828
export type RootState = ReturnType<typeof rootReducer>
@@ -33,7 +33,7 @@ export type RootState = ReturnType<typeof rootReducer>
3333
If you want to get the `Dispatch` type from your store, you can extract it after creating the store.
3434
It is recommend to give the type a different name like `AppDispatch` to prevent confusion, as the type name `Dispatch` is usually overused.
3535
36-
```typescript
36+
```typescript {6}
3737
import { configureStore } from '@reduxjs/toolkit'
3838
import rootReducer from './rootReducer'
3939
const store = configureStore({
@@ -50,7 +50,7 @@ There might however be cases, where TypeScript decides to simplify your provided
5050
5151
Please note that when calling `getDefaultMiddleware` in TypeScript, you have to provide the state type as a generic argument.
5252
53-
```ts
53+
```ts {10-20}
5454
import { configureStore } from '@reduxjs/toolkit'
5555
import additionalMiddleware from 'additional-middleware'
5656
// @ts-ignore
@@ -110,7 +110,7 @@ createAction('test', withPayloadType<string>())
110110

111111
### Alternative to using a literally-typed `action.type`
112112

113-
If you are using `action.type` as discriminator on a discriminated union, for example to correctly type your payload in `case` statements, you might be interested in this alternative:
113+
If you are using `action.type` as a discriminator on a discriminated union, for example to correctly type your payload in `case` statements, you might be interested in this alternative:
114114

115115
Created action creators have a `match` method that acts as a [type predicate](https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates):
116116

@@ -128,7 +128,7 @@ This `match` method is also very useful in combination with `redux-observable` a
128128

129129
## `createReducer`
130130

131-
The default way of calling `createReducer` would be with a map object, like this:
131+
The default way of calling `createReducer` would be with a "lookup table" / "map object", like this:
132132

133133
```typescript
134134
createReducer(0, {
@@ -159,7 +159,7 @@ As an alternative, RTK includes a type-safe reducer builder API.
159159

160160
Instead of using a simple object as an argument to `createReducer`, you can also use a callback that receives a `ActionReducerMapBuilder` instance:
161161

162-
```typescript
162+
```typescript {3-10}
163163
const increment = createAction<number, 'increment'>('increment')
164164
const decrement = createAction<number, 'decrement'>('decrement')
165165
createReducer(0, builder =>
@@ -213,33 +213,41 @@ createSlice({
213213
})
214214
```
215215

216-
### Defining the type of your `initialState`
216+
### Defining the Initial State Type
217217

218218
You might have noticed that it is not a good idea to pass your `SliceState` type as a generic to `createSlice`. This is due to the fact that in almost all cases, follow-up generic parameters to `createSlice` need to be inferred, and TypeScript cannot mix explicit declaration and inference of generic types within the same "generic block".
219219

220-
Instead, you can use the construct `initialState: myInitialState as SliceState`.
220+
The standard approach is to declare an interface or type for your state, create an initial state value that uses that type, and pass the initial state value to `createSlice. You can also use the construct`initialState: myInitialState as SliceState`.
221221

222-
```ts
222+
```ts {1,4,8,15}
223223
type SliceState = { state: 'loading' } | { state: 'finished'; data: string }
224224

225+
// First approach: define the initial state using that type
226+
const initialState: SliceState = { state: 'loading' }
227+
225228
createSlice({
226-
name: 'test',
229+
name: 'test1',
230+
initialState, // type SliceState is inferred for the state of the slice
231+
reducers: {}
232+
})
233+
234+
// Or, cast the initial state as necessary
235+
createSlice({
236+
name: 'test2',
227237
initialState: { state: 'loading' } as SliceState,
228-
reducers: {
229-
// ...
230-
}
238+
reducers: {}
231239
})
232240
```
233241

234242
which will result in a `Slice<SliceState, ...>`.
235243

236-
### The `prepare` notation and typing the `meta` and `error` action property
244+
### Defining Action Contents with `prepare` Callbacks
237245

238-
If you want to add a `meta` or `error` property to your action, you have to use the `prepare` notation.
246+
If you want to add a `meta` or `error` property to your action, or customize the `payload` of your action, you have to use the `prepare` notation.
239247

240248
Using this notation with TypeScript looks like this:
241249

242-
```ts
250+
```ts {5-16}
243251
const blogSlice = createSlice({
244252
name: 'blogData',
245253
initialState,
@@ -260,13 +268,13 @@ const blogSlice = createSlice({
260268
})
261269
```
262270

263-
### On the "type" property of slice action Reducers
271+
### Generated Action Types for Slices
264272

265-
As TS cannot combine two string literals (`slice.name` and the key of `actionMap`) into a new literal, all actionCreators created by createSlice are of type 'string'. This is usually not a problem, as these types are only rarely used as literals.
273+
As TS cannot combine two string literals (`slice.name` and the key of `actionMap`) into a new literal, all actionCreators created by `createSlice` are of type 'string'. This is usually not a problem, as these types are only rarely used as literals.
266274

267-
In most cases that type would be required as a literal, the `slice.action.myAction.match` [type predicate](https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates) should prove as a viable alternative:
275+
In most cases that `type` would be required as a literal, the `slice.action.myAction.match` [type predicate](https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates) should be a viable alternative:
268276

269-
```typescript
277+
```ts {10}
270278
const slice = createSlice({
271279
name: 'test',
272280
initialState: 0,
@@ -286,7 +294,44 @@ If you actually _need_ that type, unfortunately there is no other way than manua
286294

287295
### Type safety with `extraReducers`
288296

289-
Like in `createReducer`, the `extraReducers` map object is not easy to fully type. So, like with `createReducer`, you may also use the "builder callback" approach for defining the reducer object argument. See [the `createReducer` section above](#createreducer) for an example.
297+
Reducer lookup tables that map an action `type` string to a reducer function are not easy to fully type correctly. This affects both `createReducer` and the `extraReducers` argument for `createSlice`. So, like with `createReducer`, [you may also use the "builder callback" approach](#building-type-safe-reducer-argument-objects) for defining the reducer object argument.
298+
299+
This is particularly useful when a slice reducer needs to handle action types generated by other slices, or generated by specific calls to `createAction` (such as the actions generated by [`createAsyncThunk`](../api/createAsyncThunk.md)).
300+
301+
```ts {27-30}
302+
const fetchUserById = createAsyncThunk(
303+
'users/fetchById',
304+
// if you type your function argument here
305+
async (userId: number) => {
306+
const response = await fetch(`https://reqres.in/api/users/${userId}`)
307+
return (await response.json()) as Returned
308+
}
309+
)
310+
311+
interface UsersState {
312+
entities: []
313+
loading: 'idle' | 'pending' | 'succeeded' | 'failed'
314+
}
315+
316+
const initialState: UsersState = {
317+
entities: [],
318+
loading: 'idle'
319+
}
320+
321+
const usersSlice = createSlice({
322+
name: 'users',
323+
initialState,
324+
reducers: {
325+
// fill in primary logic here
326+
},
327+
extraReducers: builder => {
328+
builder.addCase(fetchUserById.pending, (state, action) => {
329+
// both `state` and `action` are now correctly typed
330+
// based on the slice state and the `pending` action creator
331+
})
332+
}
333+
})
334+
```
290335

291336
### Wrapping `createSlice`
292337

@@ -349,11 +394,12 @@ const wrappedSlice = createGenericSlice({
349394

350395
## `createAsyncThunk`
351396

352-
In the most common use cases, you might not need any special types for `createAsyncThunk`.
353-
Just provide a type for the first argument to the `payloadCreator` argument as you would for any function argument and the resulting thunk will accept the same type as input parameter.
397+
In the most common use cases, you should not need to explicitly declare any types for the `createAsyncThunk` call itself.
398+
399+
Just provide a type for the first argument to the `payloadCreator` argument as you would for any function argument, and the resulting thunk will accept the same type as input parameter.
354400
The return type of the `payloadCreator` will also be reflected in all generated action types.
355401

356-
```ts
402+
```ts {8,17}
357403
interface Returned {
358404
// ...
359405
}
@@ -373,9 +419,11 @@ const fetchUserById = createAsyncThunk(
373419
const lastReturnedAction = await store.dispatch(fetchUserById(3))
374420
```
375421

376-
If you want to use `thunkApi.dispatch`, `thunkApi.getStore()` or `thunkApi.extra` from within the `payloadCreator`, you will need to define some generic arguments, as the types for these arguments cannot be inferred. Also, as TS cannot mix explicit and inferred generic parameters, from this point on you'll have to define the `Returned` and `ThunkArg` generic parameter as well.
422+
The second argument to the `payloadCreator` is a `thunkApi` object containing references to the `dispatch`, `getState`, and `extra` arguments from the thunk middleware. If you want to use these from within the `payloadCreator`, you will need to define some generic arguments, as the types for these arguments cannot be inferred. Also, as TS cannot mix explicit and inferred generic parameters, from this point on you'll have to define the `Returned` and `ThunkArg` generic parameter as well.
377423

378-
```ts
424+
To define the types for these arguments, pass an object as the third generic argument, with type declarations for some or all of these fields: `{dispatch?, state?, extra?}`.
425+
426+
```ts {2-10}
379427
const fetchUserById = createAsyncThunk<
380428
Returned,
381429
number,
@@ -396,15 +444,15 @@ const fetchUserById = createAsyncThunk<
396444
})
397445
```
398446

399-
While this notation for `state`, `dispatch` and `extra` might seem uncommon at first, it allows you to provide only the types for these you actually need - so for example, if you are not accessing `getState` within your `actionProducer`, there is no need to provide a type for `state`.
447+
While this notation for `state`, `dispatch` and `extra` might seem uncommon at first, it allows you to provide only the types for these you actually need - so for example, if you are not accessing `getState` within your `payloadCreator`, there is no need to provide a type for `state`.
400448

401449
## `createEntityAdapter`
402450

403-
Typing `createEntityAdapter` only requires you to specify the entity type as the only generic argument.
451+
Typing `createEntityAdapter` only requires you to specify the entity type as the single generic argument.
404452

405-
So the example from the `createEntityAdapter` documentation would look like this in TypeScript:
453+
The example from the `createEntityAdapter` documentation would look like this in TypeScript:
406454

407-
```ts
455+
```ts {7}
408456
interface Book {
409457
bookId: number
410458
title: string

0 commit comments

Comments
 (0)