Skip to content

Commit 86b6fe0

Browse files
authoredMar 21, 2021
Additional TS usage updates (#1698)
1 parent 9005c4a commit 86b6fe0

File tree

2 files changed

+166
-114
lines changed

2 files changed

+166
-114
lines changed
 

‎docs/using-react-redux/usage-with-typescript.md

+122-93
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,71 @@ sidebar_label: Usage with TypeScript
77

88
# Usage with TypeScript
99

10-
React-Redux itself is currently written in plain JavaScript. However, it works well with static type systems such as TypeScript and Flow.
10+
React-Redux itself is currently written in plain JavaScript. However, it works well with static type systems such as TypeScript.
1111

1212
React-Redux doesn't ship with its own type definitions. If you are using TypeScript you should install the [`@types/react-redux` type definitions](https://npm.im/@types/react-redux) from NPM. In addition to typing the library functions, the types also export some helpers to make it easier to write typesafe interfaces between your Redux store and your React components.
1313

14-
## Defining the Root State Type
1514

16-
Both `mapState` and `useSelector` depend on declaring the type of the complete Redux store state value. While this type could be written by hand, the easiest way to define it is to have TypeScript infer it based on what your root reducer function returns. This way, the type is automatically updated as the reducer functions are modified.
15+
## Standard Redux Toolkit Project Setup with TypeScript
1716

18-
```ts
19-
// rootReducer.ts
20-
export const rootReducer = combineReducers({
21-
posts: postsReducer,
22-
comments: commentsReducer,
23-
users: usersReducer,
17+
We assume that a typical Redux project is using Redux Toolkit and React Redux together.
18+
19+
[Redux Toolkit](https://redux-toolkit.js.org) (RTK) is the standard approach for writing modern Redux logic. RTK is already written in TypeScript, and its API is designed to provide a good experience for TypeScript usage.
20+
21+
The [Redux+TS template for Create-React-App](https://github.com/reduxjs/cra-template-redux-typescript) comes with a working example of these patterns already configured.
22+
23+
### Define Root State and Dispatch Types
24+
25+
Using [configureStore](https://redux-toolkit.js.org/api/configureStore) should not need any additional typings. You will, however, want to extract the `RootState` type and the `Dispatch` type so that they can be referenced as needed. Inferring these types from the store itself means that they correctly update as you add more state slices or modify middleware settings.
26+
27+
Since those are types, it's safe to export them directly from your store setup file such as `app/store.ts` and import them directly into other files.
28+
29+
```ts title="app/store.ts"
30+
import { configureStore } from '@reduxjs/toolkit'
31+
// ...
32+
33+
const store = configureStore({
34+
reducer: {
35+
posts: postsReducer,
36+
comments: commentsReducer,
37+
users: usersReducer,
38+
}
2439
})
2540

26-
export type RootState = ReturnType<typeof rootReducer>
27-
// {posts: PostsState, comments: CommentsState, users: UsersState}
41+
// highlight-start
42+
// Infer the `RootState` and `AppDispatch` types from the store itself
43+
export type RootState = ReturnType<typeof store.getState>
44+
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
45+
export type AppDispatch = typeof store.dispatch
46+
// highlight-end
47+
```
48+
49+
### Define Typed Hooks
50+
51+
While it's possible to import the `RootState` and `AppDispatch` types into each component, it's better to **create pre-typed versions of the `useDispatch` and `useSelector` hooks for usage in your application**. This is important for a couple reasons:
52+
53+
- For `useSelector`, it saves you the need to type `(state: RootState)` every time
54+
- For `useDispatch`, the default `Dispatch` type does not know about thunks or other middleware. In order to correctly dispatch thunks, you need to use the specific customized `AppDispatch` type from the store that includes the thunk middleware types, and use that with `useDispatch`. Adding a pre-typed `useDispatch` hook keeps you from forgetting to import `AppDispatch` where it's needed.
55+
56+
Since these are actual variables, not types, it's important to define them in a separate file such as `app/hooks.ts`, not the store setup file. This allows you to import them into any component file that needs to use the hooks, and avoids potential circular import dependency issues.
57+
58+
```ts title="app/hooks.ts"
59+
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
60+
import type { RootState, AppDispatch } from './store'
61+
62+
// highlight-start
63+
// Use throughout your app instead of plain `useDispatch` and `useSelector`
64+
export const useAppDispatch = () => useDispatch<AppDispatch>()
65+
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
66+
// highlight-end
2867
```
2968

30-
## Typing the `useSelector` hook
69+
70+
## Typing Hooks Manually
71+
72+
We recommend using the pre-typed `useAppSelector` and `useAppDispatch` hooks shown above. If you prefer not to use those, here is how to type the hooks by themselves.
73+
74+
### Typing the `useSelector` hook
3175

3276
When writing selector functions for use with `useSelector`, you should explicitly define the type of the `state` parameter. TS should be able to then infer the return type of the selector, which will be reused as the return type of the `useSelector` hook:
3377

@@ -43,25 +87,14 @@ const selectIsOn = (state: RootState) => state.isOn
4387
const isOn = useSelector(selectIsOn)
4488
```
4589

46-
If you want to avoid repeating the `state` type declaration, you can define a typed `useSelector` hook using a helper type exported by `@types/react-redux`:
90+
This can also be done inline as well:
4791

4892
```ts
49-
// reducer.ts
50-
import { useSelector, TypedUseSelectorHook } from 'react-redux'
51-
52-
interface RootState {
53-
isOn: boolean
54-
}
55-
56-
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector
57-
58-
// my-component.tsx
59-
import { useTypedSelector } from './reducer.ts'
60-
61-
const isOn = useTypedSelector((state) => state.isOn)
93+
const isOn = useSelector( (state: RootState) => state.isOn)
6294
```
6395

64-
## Typing the `useDispatch` hook
96+
97+
### Typing the `useDispatch` hook
6598

6699
By default, the return value of `useDispatch` is the standard `Dispatch` type defined by the Redux core types, so no declarations are needed:
67100

@@ -79,14 +112,72 @@ export type AppDispatch = typeof store.dispatch
79112
const dispatch: AppDispatch = useDispatch()
80113
```
81114

82-
You may also find it to be more convenient to export a hook like `useAppDispatch` shown below, then using it wherever you'd call `useDispatch`:
115+
116+
## Typing the `connect` higher order component
117+
118+
119+
### Inferring The Connected Props Automatically
120+
121+
`connect` consists of two functions that are called sequentially. The first function accepts `mapState` and `mapDispatch` as arguments, and returns a second function. The second function accepts the component to be wrapped, and returns a new wrapper component that passes down the props from `mapState` and `mapDispatch`. Normally, both functions are called together, like `connect(mapState, mapDispatch)(MyComponent)`.
122+
123+
As of v7.1.2, the `@types/react-redux` package exposes a helper type, `ConnectedProps`, that can extract the return types of `mapStateToProps` and `mapDispatchToProps` from the first function. This means that if you split the `connect` call into two steps, all of the "props from Redux" can be inferred automatically without having to write them by hand. While this approach may feel unusual if you've been using React-Redux for a while, it does simplify the type declarations considerably.
83124

84125
```ts
85-
export type AppDispatch = typeof store.dispatch
86-
export const useAppDispatch = () => useDispatch<AppDispatch>() // Export a hook that can be reused to resolve types
126+
import { connect, ConnectedProps } from 'react-redux'
127+
128+
interface RootState {
129+
isOn: boolean
130+
}
131+
132+
const mapState = (state: RootState) => ({
133+
isOn: state.isOn,
134+
})
135+
136+
const mapDispatch = {
137+
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
138+
}
139+
140+
const connector = connect(mapState, mapDispatch)
141+
142+
// The inferred type will look like:
143+
// {isOn: boolean, toggleOn: () => void}
144+
type PropsFromRedux = ConnectedProps<typeof connector>
87145
```
88146
89-
## Typing the `connect` higher order component
147+
The return type of `ConnectedProps` can then be used to type your props object.
148+
149+
```tsx
150+
interface Props extends PropsFromRedux {
151+
backgroundColor: string
152+
}
153+
154+
const MyComponent = (props: Props) => (
155+
<div style={{ backgroundColor: props.backgroundColor }}>
156+
<button onClick={props.toggleOn}>
157+
Toggle is {props.isOn ? 'ON' : 'OFF'}
158+
</button>
159+
</div>
160+
)
161+
162+
export default connector(MyComponent)
163+
```
164+
165+
Because types can be defined in any order, you can still declare your component before declaring the connector if you want.
166+
167+
```tsx
168+
// alternately, declare `type Props = PropsFromRedux & {backgroundColor: string}`
169+
interface Props extends PropsFromRedux {
170+
backgroundColor: string;
171+
}
172+
173+
const MyComponent = (props: Props) => /* same as above */
174+
175+
const connector = connect(/* same as above*/)
176+
177+
type PropsFromRedux = ConnectedProps<typeof connector>
178+
179+
export default connector(MyComponent)
180+
```
90181

91182
### Manually Typing `connect`
92183

@@ -151,68 +242,6 @@ type Props = StateProps & DispatchProps & OwnProps
151242
152243
However, inferring the type of `mapDispatch` this way will break if it is defined as an object and also refers to thunks.
153244
154-
### Inferring The Connected Props Automatically
155-
156-
`connect` consists of two functions that are called sequentially. The first function accepts `mapState` and `mapDispatch` as arguments, and returns a second function. The second function accepts the component to be wrapped, and returns a new wrapper component that passes down the props from `mapState` and `mapDispatch`. Normally, both functions are called together, like `connect(mapState, mapDispatch)(MyComponent)`.
157-
158-
As of v7.1.2, the `@types/react-redux` package exposes a helper type, `ConnectedProps`, that can extract the return types of `mapStateToProps` and `mapDispatchToProps` from the first function. This means that if you split the `connect` call into two steps, all of the "props from Redux" can be inferred automatically without having to write them by hand. While this approach may feel unusual if you've been using React-Redux for a while, it does simplify the type declarations considerably.
159-
160-
```ts
161-
import { connect, ConnectedProps } from 'react-redux'
162-
163-
interface RootState {
164-
isOn: boolean
165-
}
166-
167-
const mapState = (state: RootState) => ({
168-
isOn: state.isOn,
169-
})
170-
171-
const mapDispatch = {
172-
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
173-
}
174-
175-
const connector = connect(mapState, mapDispatch)
176-
177-
// The inferred type will look like:
178-
// {isOn: boolean, toggleOn: () => void}
179-
type PropsFromRedux = ConnectedProps<typeof connector>
180-
```
181-
182-
The return type of `ConnectedProps` can then be used to type your props object.
183-
184-
```tsx
185-
interface Props extends PropsFromRedux {
186-
backgroundColor: string
187-
}
188-
189-
const MyComponent = (props: Props) => (
190-
<div style={{ backgroundColor: props.backgroundColor }}>
191-
<button onClick={props.toggleOn}>
192-
Toggle is {props.isOn ? 'ON' : 'OFF'}
193-
</button>
194-
</div>
195-
)
196-
197-
export default connector(MyComponent)
198-
```
199-
200-
Because types can be defined in any order, you can still declare your component before declaring the connector if you want.
201-
202-
```tsx
203-
// alternately, declare `type Props = PropsFromRedux & {backgroundColor: string}`
204-
interface Props extends PropsFromRedux {
205-
backgroundColor: string;
206-
}
207-
208-
const MyComponent = (props: Props) => /* same as above */
209-
210-
const connector = connect(/* same as above*/)
211-
212-
type PropsFromRedux = ConnectedProps<typeof connector>
213-
214-
export default connector(MyComponent)
215-
```
216245
217246
## Recommendations
218247

‎website/sidebars.js

+44-21
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,46 @@
11
module.exports = {
2-
docs: {
3-
Introduction: [
4-
'introduction/getting-started',
5-
'introduction/why-use-react-redux'
6-
],
7-
Tutorials: ['tutorials/connect'],
8-
'Using React Redux': [
9-
'using-react-redux/usage-with-typescript',
10-
'using-react-redux/connect-mapstate',
11-
'using-react-redux/connect-mapdispatch',
12-
'using-react-redux/accessing-store'
13-
],
14-
'API Reference': [
15-
'api/provider',
16-
'api/hooks',
17-
'api/connect',
18-
'api/connect-advanced',
19-
'api/batch'
20-
],
21-
Guides: ['troubleshooting']
22-
}
2+
docs: [
3+
{
4+
type: 'category',
5+
label: 'Introduction',
6+
collapsed: false,
7+
items: [
8+
'introduction/getting-started',
9+
'introduction/why-use-react-redux'
10+
]
11+
},
12+
{
13+
type: 'category',
14+
label: 'Tutorials',
15+
collapsed: false,
16+
items: ['tutorials/connect']
17+
},
18+
{
19+
type: 'category',
20+
label: 'Using React Redux',
21+
collapsed: false,
22+
items: [
23+
'using-react-redux/usage-with-typescript',
24+
'using-react-redux/connect-mapstate',
25+
'using-react-redux/connect-mapdispatch',
26+
'using-react-redux/accessing-store'
27+
]
28+
},
29+
{
30+
type: 'category',
31+
label: 'API Reference',
32+
items: [
33+
'api/provider',
34+
'api/hooks',
35+
'api/connect',
36+
'api/connect-advanced',
37+
'api/batch'
38+
]
39+
},
40+
{
41+
type: 'category',
42+
label: 'Guides',
43+
items: ['troubleshooting']
44+
}
45+
]
2346
}

0 commit comments

Comments
 (0)
Please sign in to comment.