Skip to content

Commit 436834f

Browse files
committed
Document casting getState
1 parent 6a09010 commit 436834f

File tree

3 files changed

+98
-7
lines changed

3 files changed

+98
-7
lines changed

docs/api/createSlice.mdx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -304,16 +304,25 @@ Typing for the `create.asyncThunk` works in the same way as [`createAsyncThunk`]
304304
305305
A type for `state` and/or `dispatch` _cannot_ be provided as part of the `ThunkApiConfig`, as this would cause circular types.
306306

307-
Instead, it is necessary to assert the type when needed.
307+
Instead, it is necessary to assert the type when needed - `getState() as RootState`. You may also include an explicit return type for the payload function as well, in order to break the circular type inference cycle.
308308

309309
```ts no-transpile
310310
create.asyncThunk<Todo, string, { rejectValue: { error: string } }>(
311-
async (id, thunkApi) => {
311+
// highlight-start
312+
// may need to include an explicit return type
313+
async (id: string, thunkApi): Promise<Todo> => {
314+
// Cast types for `getState` and `dispatch` manually
312315
const state = thunkApi.getState() as RootState
313316
const dispatch = thunkApi.dispatch as AppDispatch
314-
throw thunkApi.rejectWithValue({
315-
error: 'Oh no!',
316-
})
317+
// highlight-end
318+
try {
319+
const todo = await fetchTodo()
320+
return todo
321+
} catch (e) {
322+
throw thunkApi.rejectWithValue({
323+
error: 'Oh no!',
324+
})
325+
}
317326
}
318327
)
319328
```

docs/rtk-query/usage-with-typescript.mdx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,32 @@ const api = createApi({
390390
})
391391
```
392392

393+
### Typing `dispatch` and `getState`
394+
395+
`createApi` exposes the standard Redux `dispatch` and `getState` methods in several places, such as the `lifecycleApi` argument in lifecycle methods, or the `baseQueryApi` argument passed to `queryFn` methods and base query functions.
396+
397+
Normally, [your application infers `RootState` and `AppDispatch` types from the store setup](../tutorials/typescript.md#define-root-state-and-dispatch-types). Since `createApi` has to be called prior to creating the Redux store and is used as part of the store setup sequence, it can't directly know or use those types - it would cause a circular type inference error.
398+
399+
By default, `dispatch` usages inside of `createApi` will be typed as `ThunkDispatch`, and `getState` usages are typed as `() => unknown`. You will need to assert the type when needed - `getState() as RootState`. You may also include an explicit return type for the function as well, in order to break the circular type inference cycle:
400+
401+
```ts no-transpile
402+
const api = createApi({
403+
baseQuery,
404+
endpoints: (build) => ({
405+
getTodos: build.query<Todo[], void>({
406+
async queryFn() {
407+
// highlight-start
408+
// Cast state as `RootState`
409+
const state = getState() as RootState
410+
// highlight-end
411+
const text = state.todoTexts[queryFnCalls]
412+
return { data: [{ id: `${queryFnCalls++}`, text }] }
413+
},
414+
}),
415+
}),
416+
})
417+
```
418+
393419
### Typing `providesTags`/`invalidatesTags`
394420

395421
RTK Query utilizes a cache tag invalidation system in order to provide [automated re-fetching](./usage/automated-refetching.mdx) of stale data.

docs/usage/usage-with-typescript.md

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,9 +326,9 @@ const blogSlice = createSlice({
326326

327327
### Generated Action Types for Slices
328328

329-
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.
329+
`createSlice` generates action type strings by combining the `name` field from the slice with the field name of the reducer function, like `'test/increment'`. This is strongly typed as the exact value, thanks to TS's string literal analysis.
330330

331-
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:
331+
You can also use the `slice.action.myAction.match` [type predicate](https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates) should be a viable alternative:
332332

333333
```ts {10}
334334
const slice = createSlice({
@@ -339,6 +339,9 @@ const slice = createSlice({
339339
},
340340
})
341341

342+
type incrementType = typeof slice.actions.increment.type
343+
// type incrementType = 'test/increment'
344+
342345
function myCustomMiddleware(action: Action) {
343346
if (slice.actions.increment.match(action)) {
344347
// `action` is narrowed down to the type `PayloadAction<number>` here.
@@ -408,6 +411,59 @@ type AtLeastOne<T extends Record<string, any>> = keyof T extends infer K
408411
type AtLeastOneUserField = AtLeastOne<User>
409412
```
410413
414+
### Typing Async Thunks Inside `createSlice`
415+
416+
As of 2.0, `createSlice` allows [defining thunks inside of `reducers` using a callback syntax](../api/createSlice.mdx/#the-reducers-creator-callback-notation).
417+
418+
Typing for the `create.asyncThunk` method works in the same way as [`createAsyncThunk`](#createasyncthunk), with one key difference.
419+
420+
A type for `state` and/or `dispatch` _cannot_ be provided as part of the `ThunkApiConfig`, as this would cause circular types.
421+
422+
Instead, it is necessary to assert the type when needed - `getState() as RootState`. You may also include an explicit return type for the payload function as well, in order to break the circular type inference cycle.
423+
424+
```ts no-transpile
425+
create.asyncThunk<Todo, string, { rejectValue: { error: string } }>(
426+
// highlight-start
427+
// may need to include an explicit return type
428+
async (id: string, thunkApi): Promise<Todo> => {
429+
// Cast types for `getState` and `dispatch` manually
430+
const state = thunkApi.getState() as RootState
431+
const dispatch = thunkApi.dispatch as AppDispatch
432+
// highlight-end
433+
try {
434+
const todo = await fetchTodo()
435+
return todo
436+
} catch (e) {
437+
throw thunkApi.rejectWithValue({
438+
error: 'Oh no!',
439+
})
440+
}
441+
}
442+
)
443+
```
444+
445+
For common thunk API configuration options, a [`withTypes` helper](../usage/usage-with-typescript#defining-a-pre-typed-createasyncthunk) is provided:
446+
447+
```ts no-transpile
448+
reducers: (create) => {
449+
const createAThunk =
450+
create.asyncThunk.withTypes<{ rejectValue: { error: string } }>()
451+
452+
return {
453+
fetchTodo: createAThunk<Todo, string>(async (id, thunkApi) => {
454+
throw thunkApi.rejectWithValue({
455+
error: 'Oh no!',
456+
})
457+
}),
458+
fetchTodos: createAThunk<Todo[], string>(async (id, thunkApi) => {
459+
throw thunkApi.rejectWithValue({
460+
error: 'Oh no, not again!',
461+
})
462+
}),
463+
}
464+
}
465+
```
466+
411467
### Wrapping `createSlice`
412468
413469
If you need to reuse reducer logic, it is common to write ["higher-order reducers"](https://redux.js.org/recipes/structuring-reducers/reusing-reducer-logic#customizing-behavior-with-higher-order-reducers) that wrap a reducer function with additional common behavior. This can be done with `createSlice` as well, but due to the complexity of the types for `createSlice`, you have to use the `SliceCaseReducers` and `ValidateSliceCaseReducers` types in a very specific way.

0 commit comments

Comments
 (0)