I found a way to get type safety when using mapStateToProps
from react-redux
: as documented you can define an interface and parameterize React.Component<T>
with your interface.
However, when I am defining mapStateToProps
, I'm already defining a function where the types of the properties of the resulting object can be inferred. Eg,
function mapStateToProps(state: MyState) {
return {
counter: state.counter
};
}
Here, the prop counter
can be inferred to be the same type as state.counter
. But I still have to have boilerplate code like the following:
interface AppProps {
counter: number;
}
class App extends React.Component<AppProps> { ... }
export default connect(mapStateToProps)(App);
So the question is, is there any way to structure the code so that I can avoid writing the type of counter
twice? Or to avoid parameterizing the type of React.Component
-- even if I could have the props of the ponent inferred from an explicitly-hinted result type of the mapStateToProps
function, that would be preferable. I am wondering if the duplication above is indeed the normal way to write typed ponents using React-Redux.
I found a way to get type safety when using mapStateToProps
from react-redux
: as documented you can define an interface and parameterize React.Component<T>
with your interface.
However, when I am defining mapStateToProps
, I'm already defining a function where the types of the properties of the resulting object can be inferred. Eg,
function mapStateToProps(state: MyState) {
return {
counter: state.counter
};
}
Here, the prop counter
can be inferred to be the same type as state.counter
. But I still have to have boilerplate code like the following:
interface AppProps {
counter: number;
}
class App extends React.Component<AppProps> { ... }
export default connect(mapStateToProps)(App);
So the question is, is there any way to structure the code so that I can avoid writing the type of counter
twice? Or to avoid parameterizing the type of React.Component
-- even if I could have the props of the ponent inferred from an explicitly-hinted result type of the mapStateToProps
function, that would be preferable. I am wondering if the duplication above is indeed the normal way to write typed ponents using React-Redux.
3 Answers
Reset to default 11Yes. There's a neat technique for inferring the type of the bined props that connect
will pass to your ponent based on mapState
and mapDispatch
.
There is a new ConnectedProps<T>
type that is available in @types/[email protected]
. You can use it like this:
function mapStateToProps(state: MyState) {
return {
counter: state.counter
};
}
const mapDispatch = {increment};
// Do the first half of the `connect()` call separately,
// before declaring the ponent
const connector = connect(mapState, mapDispatch);
// Extract "the type of the props passed down by connect"
type PropsFromRedux = ConnectedProps<typeof connector>
// should be: {counter: number, increment: () => {type: "INCREMENT"}}, etc
// define bined props
type MyComponentProps = PropsFromRedux & PropsFromParent;
// Declare the ponent with the right props type
class MyComponent extends React.Component<MyComponentProps> {}
// Finish the connect call
export default connector(MyComponent)
Note that this correctly infers the type of thunk action creators included in mapDispatch
if it's an object, whereas typeof mapDispatch
does not.
We will add this to the official React-Redux docs as a remended approach soon.
More details:
- Gist: ConnectedProps - the missing TS helper for Redux
- Practical TypeScript with React+Redux
- DefinitelyTyped #31227: Connected ponent inference
- DefinitelyTyped PR #37300: Add ConnectedProps type
I don't think so. You could make your setup more concise by using Redux hooks: https://react-redux.js/next/api/hooks
// Your function ponent . You don't need to connect it
const App: React.FC = () => {
const counter = useSelector<number>((state: MyState) => state.counter);
const dispatch = useDispatch(); // for dispatching actions
};
Edit: You can if you just use the same MyState
type. But I don't think you would want that.
I would type mapped dispatch props, and ponent props separately and then bine the inferred type of the mapped state to props function. See below for a quick example. There might be a more elegant solution but hopefully, it will hopefully get you on the right track.
import * as React from "react";
import { Action } from "redux";
import { connect } from "react-redux";
// Lives in some lib file
type AppState = {
counter: number;
};
type MappedState = {
putedValue: number;
};
type MappedDispatch = {
doSomethingCool: () => Action;
};
type ComponentProps = {
someProp: string;
};
const mapStateToProps = (state: AppState) => ({
putedValue: state.counter
});
const mapDispatchToProps: MappedDispatch = {
doSomethingCool: () => {
return {
type: "DO_SOMETHING_COOL"
};
}
};
type Props = ReturnType<typeof mapStateToProps> &
MappedDispatch &
ComponentProps;
class DumbComponent extends React.Component<Props> {
render() {
return (
<div>
<h1>{this.props.someProp}</h1>
<div>{this.props.putedValue}</div>
<button onClick={() => this.props.doSomethingCool()}>Click me</button>
</div>
);
}
}
const SmartComponent = connect(
mapStateToProps,
mapDispatchToProps
)(DumbComponent);
export default SmartComponent;