How to properly type a Redux connect call?
Option 1: Split IComponentProps
The simplest way to do this is probably just defining separate interfaces for your "state derived props", your "own props" and your "dispatch props" and then use an intersection type to join them together for the IComponentProps
import * as React from 'react';
import { connect, Dispatch } from 'react-redux'
import { IStateStore } from '@src/reducers';
interface IComponentOwnProps {
foo: string;
}
interface IComponentStoreProps {
bar: string;
}
interface IComponentDispatchProps {
fooAction: () => void;
}
type IComponentProps = IComponentOwnProps & IComponentStoreProps & IComponentDispatchProps
class IComponent extends React.Component<IComponentProps, never> {
public render() {
return (
<div>
foo: {this.props.foo}
bar: {this.props.bar}
<button onClick={this.props.fooAction}>Do Foo</button>
</div>
);
}
}
export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
(state, ownProps): IComponentStoreProps => {
return {
bar: state.bar + ownProps.foo
};
},
(dispatch: Dispatch<IStateStore>): IComponentDispatchProps => (
{
fooAction: () => dispatch({type:'FOO_ACTION'})
}
)
)(IComponent);
We can set the connect function generic parameters like so:
<TStateProps, TDispatchProps, TOwnProps, State>
Option 2: Let your functions define your Props interface
Another option I've seen in the wild is to leveraging the ReturnType
mapped type to allow your mapX2Props
functions to actually define what they contribute to IComponentProps
.
type IComponentProps = IComponentOwnProps & IComponentStoreProps & IComponentDispatchProps;
interface IComponentOwnProps {
foo: string;
}
type IComponentStoreProps = ReturnType<typeof mapStateToProps>;
type IComponentDispatchProps = ReturnType<typeof mapDispatchToProps>;
class IComponent extends React.Component<IComponentProps, never> {
//...
}
function mapStateToProps(state: IStateStore, ownProps: IComponentOwnProps) {
return {
bar: state.bar + ownProps.foo,
};
}
function mapDispatchToProps(dispatch: Dispatch<IStateStore>) {
return {
fooAction: () => dispatch({ type: 'FOO_ACTION' })
};
}
export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
mapStateToProps,
mapDispatchToProps
)(IComponent);
The big advantage here is that it reduces a little bit of the boiler plate and makes it so you only have one place to update when you add a new mapped prop.
I've always steered away from ReturnType
, simplify because it feels backwards to let your implementation define your programming interface "contracts" (IMO). It becomes almost too easy to change your IComponentProps
in a way you didn't intend.
However, since everything here is pretty self contained, it's probably an acceptable use case.
One solution is to split your component properties into state-, dispatch- and maybe own-properties:
import React from "react";
import { connect } from "react-redux";
import { deleteItem } from "./action";
import { getItemById } from "./selectors";
interface StateProps {
title: string;
}
interface DispatchProps {
onDelete: () => any;
}
interface OwnProps {
id: string;
}
export type SampleItemProps = StateProps & DispatchProps & OwnProps;
export const SampleItem: React.SFC<SampleItemProps> = props => (
<div>
<div>{props.title}</div>
<button onClick={props.onDelete}>Delete</button>
</div>
);
// You can either use an explicit mapStateToProps...
const mapStateToProps = (state: RootState, ownProps: OwnProps) : StateProps => ({
title: getItemById(state, ownProps.id)
});
// Ommitted mapDispatchToProps...
// ... and infer the types from connects arguments ...
export default connect(mapStateToProps, mapDispatchToProps)(SampleItem);
// ... or explicitly type connect and "inline" map*To*.
export default connect<StateProps, DispatchProps, OwnProps, RootState>(
(state, ownProps) => ({
title: getItemById(state, ownProps.id)
}),
(dispatch, ownProps) => ({
onDelete: () => dispatch(deleteItem(ownProps.id))
})
)(SampleItem);