I'm having some trouble figuring out how to properly type Redux containers.
Consider a simple presentational component that might look like this:
interface MyProps {
name: string;
selected: boolean;
onSelect: (name: string) => void;
}
class MyComponent extends React.Component<MyProps, {}> { }
From the perspective of this component all props are required.
Now I want to write a container that pulls all these props out of state:
function mapStateToProps(state: MyState) {
return {
name: state.my.name,
selected: state.my.selected
};
}
function mapDispatchToProps(dispatch: IDispatch) {
return {
onSelect(name: string) {
dispatch(mySelectAction(name));
}
};
}
const MyContainer = connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent);
This works, but there's a big typing problem: the mapping functions (mapStateToProps and mapDispatchToProps) have no protection that they are providing the right data to fulfill MyProps. This is prone to error, typos and poor refactoring.
I could make the mapping functions return type MyProps:
function mapStateToProps(state: MyState): MyProps { }
function mapDispatchToProps(dispatch: IDispatch): MyProps { }
However this doesn't work unless I make all MyProp props optional, so that each mapping function can return only the portion they care about. I don't want to make the props optional, because they aren't optional to the presentational component.
Another option is to split up the props for each map function and combine them for the component props:
// MyComponent
interface MyStateProps {
name: string;
selected: boolean;
}
interface MyDispatchProps {
onSelect: (name: string) => void;
}
type MyProps = MyStateProps & MyDispatchProps;
class MyComponent extends React.Component<MyProps, {}> { }
// MyContainer
function mapStateToProps(state: MyState): MyStateProps { }
function mapDispatchToProps(dispatch: IDispatch): MyDispatchProps { }
Ok, that's getting me closer to what I want, but its kind of noisy and its making me write my presentational component prop interface around the shape of a container, which I don't like.
And now a second problem arises. What if I want to put the container inside another component:
<MyContainer />
This gives compile errors that name, selected, and onSelect are all missing... but that's intentional because the container is connecting to Redux and providing those. So this pushes me back to making all component props optional, but I don't like that because they aren't really optional.
Things get worse when MyContainer has some of its own props that it wants to be passed in:
<MyContainer section="somethng" />
Now what I'm trying to do is have section a required prop of MyContainer but not a prop of MyComponent, and name, selected, and onSelect are required props of MyComponent but optional or not props at all of MyContainer. I'm totally at a loss how to express this.
Any guidence on this would be appreciated!