Skip to content

Commit f059ad0

Browse files
JNaftalimarkerikson
authored andcommitted
WIP: Static Types doc page (#1439)
* first draft of new page on static types * revised because feedback * mention that we're splitting up the export and connect call * Update "Static Types" content * Add "Static Typing" page to sidebar * Move Static Typing page to get it to show up in 7.1 sidebar
1 parent 1b39cdd commit f059ad0

File tree

3 files changed

+225
-5
lines changed

3 files changed

+225
-5
lines changed

website/sidebars.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"Using React Redux": [
99
"using-react-redux/connect-mapstate",
1010
"using-react-redux/connect-mapdispatch",
11-
"using-react-redux/accessing-store"
11+
"using-react-redux/accessing-store",
12+
"using-react-redux/static-typing"
1213
],
1314
"API Reference": [
1415
"api/connect",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
---
2+
id: version-7.1-static-typing
3+
original_id: static-typing
4+
title: Static Typing
5+
hide_title: true
6+
sidebar_label: Static Typing
7+
---
8+
9+
# Static Typing
10+
11+
React-Redux is currently written in plain JavaScript. However, it works well with static type systems such as TypeScript and Flow.
12+
13+
## TypeScript
14+
15+
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.
16+
17+
### Defining the Root State Type
18+
19+
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.
20+
21+
```ts
22+
// rootReducer.ts
23+
export const rootReducer = combineReducers({
24+
posts: postsReducer,
25+
comments: commentsReducer,
26+
users: usersReducer
27+
})
28+
29+
export type RootState = ReturnType<typeof rootReducer>
30+
// {posts: PostsState, comments: CommentsState, users: UsersState}
31+
```
32+
33+
### Typing the useSelector hook
34+
35+
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:
36+
37+
```ts
38+
interface RootState {
39+
isOn: boolean
40+
}
41+
42+
// TS infers type: (state: RootState) => boolean
43+
const selectIsOn = (state: RootState) => state.isOn
44+
45+
// TS infers `isOn` is boolean
46+
const isOn = useSelector(selectIsOn)
47+
```
48+
49+
If you want to avoid repeating the `state` type declaration, you can define a typed `useSelect` hook using a helper type exported by `@types/react-redux`:
50+
51+
```ts
52+
// reducer.ts
53+
import { useSelector, TypedUseSelectorHook } from 'react-redux'
54+
55+
interface RootState {
56+
isOn: boolean
57+
}
58+
59+
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector
60+
61+
// my-component.tsx
62+
import { useTypedSelector } from './reducer.ts'
63+
64+
const isOn = useSelector(state => state.isOn)
65+
```
66+
67+
### Typing the `useDispatch` hook
68+
69+
By default, the return value of `useDispatch` is the standard `Dispatch` type defined by the Redux core types, so no declarations are needed:
70+
71+
```ts
72+
const dispatch = useDispatch()
73+
```
74+
75+
If you have a customized version of the `Dispatch` type, you may use that type explicitly:
76+
77+
```ts
78+
// store.ts
79+
export type AppDispatch = typeof store.dispatch
80+
81+
// MyComponent.tsx
82+
const dispatch: AppDispatch = useDispatch()
83+
```
84+
85+
### Typing the `connect` higher order component
86+
87+
#### Manually Typing `connect`
88+
89+
The `connect` higher-order component is somewhat complex to type, because there are 3 sources of props: `mapStateToProps`, `mapDispatchToProps`, and props passed in from the parent component. Here's a full example of what it looks like to do that manually.
90+
91+
```tsx
92+
import { connect } from 'react-redux'
93+
94+
interface StateProps {
95+
isOn: boolean
96+
}
97+
98+
interface DispatchProps {
99+
toggleOn: () => void
100+
}
101+
102+
interface OwnProps {
103+
backgroundColor: string
104+
}
105+
106+
type Props = StateProps & DispatchProps & OwnProps
107+
108+
const mapState = (state: RootState) => ({
109+
isOn: state.isOn
110+
})
111+
112+
const mapDispatch = {
113+
toggleOn: () => ({ type: 'TOGGLE_IS_ON' })
114+
}
115+
116+
const MyComponent = (props: Props) => (
117+
<div style={{ backgroundColor: props.backgroundColor }}>
118+
<button onClick={props.toggleOn}>
119+
Toggle is {props.isOn ? 'ON' : 'OFF'}
120+
</button>
121+
</div>
122+
)
123+
124+
// Typical usage: `connect` is called after the component is defined
125+
export default connect<StateProps, DispatchProps, OwnProps>(
126+
mapState,
127+
mapDispatch
128+
)(MyComponent)
129+
```
130+
131+
It is also possible to shorten this somewhat, by inferring the types of `mapState` and `mapDispatch`:
132+
133+
```ts
134+
const mapState = (state: RootState) => ({
135+
isOn: state.isOn
136+
})
137+
138+
const mapDispatch = {
139+
toggleOn: () => ({ type: 'TOGGLE_IS_ON' })
140+
}
141+
142+
type StateProps = ReturnType<typeof mapState>
143+
type DispatchProps = typeof mapDispatch
144+
145+
type Props = StateProps & DispatchProps & OwnProps
146+
```
147+
148+
However, inferring the type of `mapDispatch` this way will break if it is defined as an object and also refers to thunks.
149+
150+
#### Inferring The Connected Props Automatically
151+
152+
`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)`.
153+
154+
As of v7.1.2, the `@types/react-redux` package exposes a helper type, `ConnectedProps`, that can extract the return types of `mapStateToProp` 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.
155+
156+
```ts
157+
import { connect, ConnectedProps } from 'react-redux'
158+
159+
interface RootState {
160+
isOn: boolean
161+
}
162+
163+
const mapState = (state: RootState) => ({
164+
isOn: state.isOn
165+
})
166+
167+
const mapDispatch = {
168+
toggleOn: () => ({ type: 'TOGGLE_IS_ON' })
169+
}
170+
171+
const connector = connect(
172+
mapState,
173+
mapDispatch
174+
)
175+
176+
// The inferred type will look like:
177+
// {isOn: boolean, toggleOn: () => void}
178+
type PropsFromRedux = ConnectedProps<typeof connector>
179+
```
180+
181+
The return type of `ConnectedProps` can then be used to type your props object.
182+
183+
```tsx
184+
interface Props extends PropsFromRedux {
185+
backgroundColor: string
186+
}
187+
188+
const MyComponent = (props: Props) => (
189+
<div style={{ backgroundColor: props.backgroundColor }}>
190+
<button onClick={props.toggleOn}>
191+
Toggle is {props.isOn ? 'ON' : 'OFF'}
192+
</button>
193+
</div>
194+
)
195+
196+
export default connector(MyComponent)
197+
```
198+
199+
Because types can be defined in any order, you can still declare your component before declaring the connector if you want.
200+
201+
```tsx
202+
// alternately, declare `type Props = Props From Redux & {backgroundColor: string}`
203+
interface Props extends PropsFromRedux {
204+
backgroundColor: string;
205+
}
206+
207+
const MyComponent = (props: Props) => /* same as above */
208+
209+
const connector = connect(/* same as above*/)
210+
211+
type PropsFromRedux = ConnectedProps<typeof connector>
212+
213+
export default connector(MyComponent)
214+
```
215+
216+
### Recommendations
217+
218+
The hooks API is generally simpler to use with static types. **If you're looking for the easiest solution for using static types with React-Redux, use the hooks API.**
219+
220+
If you're using `connect`, **we recommend using the `ConnectedProps<T>` approach for inferring the props from Redux**, as that requires the fewest explicit type declarations.

website/versioned_sidebars/version-7.1-sidebars.json

+3-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"Using React Redux": [
99
"version-7.1-using-react-redux/connect-mapstate",
1010
"version-7.1-using-react-redux/connect-mapdispatch",
11-
"version-7.1-using-react-redux/accessing-store"
11+
"version-7.1-using-react-redux/accessing-store",
12+
"version-7.1-using-react-redux/static-typing"
1213
],
1314
"API Reference": [
1415
"version-7.1-api/connect",
@@ -17,8 +18,6 @@
1718
"version-7.1-api/batch",
1819
"version-7.1-api/hooks"
1920
],
20-
"Guides": [
21-
"version-7.1-troubleshooting"
22-
]
21+
"Guides": ["version-7.1-troubleshooting"]
2322
}
2423
}

0 commit comments

Comments
 (0)