Skip to content

Commit ca71480

Browse files
authored
Merge pull request #3909 from reduxjs/feature/2.0-docs-cleanup
2 parents 97ae013 + 1fb2158 commit ca71480

16 files changed

+333
-134
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/api/getDefaultMiddleware.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const store = configureStore({
5555
// Store has all of the default middleware added, _plus_ the logger middleware
5656
```
5757

58-
It is preferable to use the chainable `.concat(...)` and `.prepend(...)` methods of the returned `Tuple` instead of the array spread operator, as the latter can lose valuable type information under some circumstances.
58+
It is preferable to use the chainable `.concat(...)` and `.prepend(...)` methods of the returned `Tuple` instead of the array spread operator, as the latter can lose valuable TS type information under some circumstances.
5959

6060
## Included Default Middleware
6161

docs/introduction/getting-started.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ you make your Redux code better.
3434

3535
### Create a React Redux App
3636

37-
The recommended way to start new apps with React and Redux is by using [our official Redux+TS template for Vite](https://github.com/reduxjs/redux-templates), or by creating a new Next.js project using [Next's `with-redux` template](https://github.com/vercel/next.js/tree/canary/examples/with-redux).
37+
The recommended way to start new apps with React and Redux Toolkit is by using [our official Redux Toolkit + TS template for Vite](https://github.com/reduxjs/redux-templates), or by creating a new Next.js project using [Next's `with-redux` template](https://github.com/vercel/next.js/tree/canary/examples/with-redux).
3838

3939
Both of these already have Redux Toolkit and React-Redux configured appropriately for that build tool, and come with a small example app that demonstrates how to use several of Redux Toolkit's features.
4040

@@ -85,8 +85,7 @@ yarn add react-redux
8585
</TabItem>
8686
</Tabs>
8787

88-
It is also available as a precompiled UMD package that defines a `window.RTK` global variable.
89-
The UMD package can be used as a [`<script>` tag](https://unpkg.com/@reduxjs/toolkit/dist/redux-toolkit.umd.js) directly.
88+
The package includes a precompiled ESM build that can be used as a [`<script type="module">` tag](https://unpkg.com/@reduxjs/toolkit/dist/redux-toolkit.browser.mjs) directly in the browser.
9089

9190
## What's Included
9291

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

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,23 @@ toc_max_heading_level: 4
1212

1313
# Migrating 1.x → 2.x
1414

15+
:::tip What You'll Learn
16+
17+
- What's changed in Redux Toolkit 2.0 and Redux core 5.0, including breaking changes and new features
18+
19+
:::
20+
1521
## Introduction
1622

1723
Redux Toolkit has been available since 2019, and today it's the standard way to write Redux apps. We've gone 4+ years without any breaking changes. Now, RTK 2.0 gives us a chance to modernize the packaging, clean up deprecated options, and tighten up some edge cases.
1824

1925
Redux Toolkit 2.0 is accompanied by major versions of all the other Redux packages: Redux core 5.0, React-Redux 9.0, Redux Thunk 3.0, and Reselect 5.0.
2026

21-
This page lists known potentially breaking changes in Redux Toolkit 2.0 and Redux core 5.0, as well as new features in Redux Toolkit 2.0. As a reminder, you should not need to actually install or use the core `redux` package directly - RTK wraps that, and re-exports all methods and types.
27+
This page lists known potentially breaking changes in Redux Toolkit 2.0 and Redux core 5.0, as well as new features in Redux Toolkit 2.0. As a reminder, **you should not need to actually install or use the core `redux` package directly** - RTK wraps that, and re-exports all methods and types.
28+
29+
In practice, **most of the "breaking" changes should not have an actual effect on end users, and we expect that many projects can just update the package versions with very few code changes needed**.
2230

23-
In practice, **most of the "breaking" changes should not have an actual effect on end users**. The changes most likely to need app code updates are:
31+
The changes most likely to need app code updates are:
2432

2533
- [Object syntax removed for `createReducer` and `createSlice.extraReducers`](#object-syntax-for-createsliceextrareducers-and-createreducer-removed)
2634
- [`configureStore.middleware` must be a callback](#configurestoremiddleware-must-be-a-callback)
@@ -66,6 +74,20 @@ We've always specifically told our users that [actions and state _must_ be seria
6674

6775
In practice, this was already true 99.99% of the time and shouldn't have any effect on users (especially those using Redux Toolkit and `createSlice`), but there may be some legacy Redux codebases that opted to use Symbols as action types.
6876

77+
#### `createStore` Deprecation
78+
79+
In [Redux 4.2.0, we marked the original `createStore` method as `@deprecated`](https://github.com/reduxjs/redux/releases/tag/v4.2.0). Strictly speaking, **this is _not_ a breaking change**, nor is it new in 5.0, but we're documenting it here for completeness.
80+
81+
**This deprecation is solely a _visual_ indicator that is meant to encourage users to [migrate their apps from legacy Redux patterns to use the modern Redux Toolkit APIs](https://redux.js.org/usage/migrating-to-modern-redux)**. The deprecation results in a visual strikethrough when imported and used, like ~~`createStore`~~, but with _no_ runtime errors or warnings.
82+
83+
**`createStore` will continue to work indefinitely, and will _not_ ever be removed**. But, today we want _all_ Redux users to be using Redux Toolkit for all of their Redux logic.
84+
85+
To fix this, there are three options:
86+
87+
- **[Follow our strong suggestion to switch over to Redux Toolkit and `configureStore`](https://redux.js.org/usage/migrating-to-modern-redux)**
88+
- Do nothing. It's just a visual strikethrough, and it doesn't affect how your code behaves. Ignore it.
89+
- Switch to using the `legacy_createStore` API that is now exported, which is the exact same function but with no `@deprecated` tag. The simplest option is to do an aliased import rename, like `import { legacy_createStore as createStore } from 'redux'`
90+
6991
<div class="typescript-only">
7092

7193
#### Typescript rewrite
@@ -78,6 +100,26 @@ Redux core v5 is now built from that TS-converted source code. In theory, this s
78100

79101
Please report any unexpected compatibility issues on [Github](https://github.com/reduxjs/redux/issues)!
80102

103+
#### `AnyAction` deprecated in favour of `UnknownAction`
104+
105+
The Redux TS types have always exported an `AnyAction` type, which is defined to have `{type: string}` and treat any other field as `any`. This makes it easy to write uses like `console.log(action.whatever)`, but unfortunately does not provide any meaningful type safety.
106+
107+
We now export an `UnknownAction` type, which treats all fields other than `action.type` as `unknown`. This encourages users to write type guards that check the action object and assert its _specific_ TS type. Inside of those checks, you can access a field with better type safety.
108+
109+
`UnknownAction` is now the default any place in the Redux source that expects an action object.
110+
111+
`AnyAction` still exists for compatibility, but has been marked as deprecated.
112+
113+
Note that [Redux Toolkit's action creators have a `.match()` method](https://redux-toolkit.js.org/api/createAction#actioncreatormatch) that acts as a useful type guard:
114+
115+
```ts
116+
if (todoAdded.match(someUnknownAction)) {
117+
// action is now typed as a PayloadAction<Todo>
118+
}
119+
```
120+
121+
You can also use the new `isAction` util to check if an unknown value is some kind of action object.
122+
81123
#### `Middleware` type changed - Middleware `action` and `next` are typed as `unknown`
82124

83125
Previously, the `next` parameter is typed as the `D` type parameter passed, and `action` is typed as the `Action` extracted from the dispatch type. Neither of these are a safe assumption:
@@ -88,6 +130,8 @@ Previously, the `next` parameter is typed as the `D` type parameter passed, and
88130

89131
We've changed `next` to be `(action: unknown) => unknown` (which is accurate, we have no idea what `next` expects or will return), and changed the `action` parameter to be `unknown` (which as above, is accurate).
90132

133+
In order to safely interact with values or access fields inside of the `action` argument, you must first do a type guard check to narrow the type, such as `isAction(action)` or `someActionCreator.match(action)`.
134+
91135
This new type is incompatible with the v4 `Middleware` type, so if a package's middleware is saying it's incompatible, check which version of Redux it's getting its types from!
92136

93137
#### `PreloadedState` type removed in favour of `Reducer` generic
@@ -119,24 +163,6 @@ This change does include some breaking changes, but overall should not have a hu
119163
- The overloads for `combineReducers` are removed in favor of a single function definition that takes the `ReducersMapObject` as its generic parameter. Removing the overloads was necessary with these changes, since sometimes it was choosing the wrong overload.
120164
- Enhancers that explicitly list the generics for the reducer will need to add the third generic.
121165
122-
#### `AnyAction` deprecated in favour of `UnknownAction`
123-
124-
The Redux TS types have always exported an `AnyAction` type, which is defined to have `{type: string}` and treat any other field as `any`. This makes it easy to write uses like `console.log(action.whatever)`, but unfortunately does not provide any meaningful type safety.
125-
126-
We now export an `UnknownAction` type, which treats all fields other than `action.type` as `unknown`. This encourages users to write type guards that check the action object and assert its _specific_ TS type. Inside of those checks, you can access a field with better type safety.
127-
128-
`UnknownAction` is now the default any place in the Redux source that expects an action object.
129-
130-
`AnyAction` still exists for compatibility, but has been marked as deprecated.
131-
132-
Note that [Redux Toolkit's action creators have a `.match()` method](https://redux-toolkit.js.org/api/createAction#actioncreatormatch) that acts as a useful type guard:
133-
134-
```ts
135-
if (todoAdded.match(someUnknownAction)) {
136-
// action is now typed as a PayloadAction<Todo>
137-
}
138-
```
139-
140166
</div>
141167
142168
### Toolkit only
@@ -499,11 +525,11 @@ In practice, we hope these are reasonable tradeoffs. Creating thunks inside of `
499525
Here's what the new callback syntax looks like:
500526

501527
```ts
502-
const createSlice = buildCreateSlice({
528+
const createSliceWithThunks = buildCreateSlice({
503529
creators: { asyncThunk: asyncThunkCreator },
504530
})
505531

506-
const todosSlice = createSlice({
532+
const todosSlice = createSliceWithThunks({
507533
name: 'todos',
508534
initialState: {
509535
loading: false,

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/rtk-query/usage/customizing-queries.mdx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,13 @@ const axiosBaseQuery =
376376
> =>
377377
async ({ url, method, data, params, headers }) => {
378378
try {
379-
const result = await axios({ url: baseUrl + url, method, data, params, headers })
379+
const result = await axios({
380+
url: baseUrl + url,
381+
method,
382+
data,
383+
params,
384+
headers,
385+
})
380386
return { data: result.data }
381387
} catch (axiosError) {
382388
const err = axiosError as AxiosError
@@ -608,7 +614,7 @@ The `retry` utility has a `fail` method property attached which can be used to b
608614

609615
```ts title="Bailing out of error re-tries"
610616
import { createApi, fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react'
611-
import type { FetchArgs } from '@reduxjs/toolkit/dist/query/fetchBaseQuery'
617+
import type { FetchArgs } from '@reduxjs/toolkit/query'
612618
interface Post {
613619
id: number
614620
name: string

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)