Strongly typing the react-redux connect with typescript
Hope you don't mind if I remove some antipatterns from the code above. Please check the comments I've added. Also I've added withRouter to illustrate the pattern better
import * as React from "react";
import { bindActionCreators } from "redux";
import { withRouter, RouteComponentProps } from "react-router";
import { connect } from "react-redux";
import { compose } from "recompose";
// OUR ROOT REDUX STORE STATE TYPE
import { State } from "../redux";
import FileExplorer from "../components/file-explorer/file-explorer";
// interfaces starting with 'I' is an antipattern and really
// rarely needed to be in a separate file
// OwnProps - that's what external code knows about out container
type OwnProps = {};
// this comes from redux
type StateProps = {
fileExplorer: FileDirectoryTree | null;
};
// resulting props - that what container expects to have
type Props = OwnProps & StateProps & RouteComponentProps<any>;
// no need to have a class, SFC will do the same job
const SideMenu: React.SFC<Props> = props => {
return (
<div>
{this.props.fileExplorerInfo !== null && (
<FileExplorer
fileExplorerDirectory={
this.props.fileExplorerInfo.fileExplorerDirectory
}
/>
)}
</div>
);
};
// compose (from recompose lib) because usually we have more than 1 hoc
// say let's add withRouter just for fun
export default compose<Props, OwnProps>(
withRouter,
// it's important to read the typings:
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-redux/index.d.ts
connect<StateProps, {}, {}, State>(s => ({
fileExplorerInfo: s.fileExplorer
})),
)(SideMenu);
When using generics, you are getting the place of interfaces wrong:
When you declare your React component:
class Comp extends Component<ICompProps, ICompState>
With ICompProps
and ICompState
are your component's props and internal state respectively.
When you use connect:
connect<IMapStateToProps, IMapDispatchToProps, ICompProps, IReduxState>
IMapStateToProps
represents what is returned by your mapStateToProps()
function.
IMapDispatchToProps
represents what is returned by your mapDispatchToProps()
function.
ICompProps
represents your React component props (same as above)
IReduxState
represents your App's Redux State
So in your particular example:
When declaring your React component:
class SideMenu extends Component<ISideMenu, {}>
Use ISideMenu
for the props and {}
(empty state) for the state as you don't use any state.
When using connect:
connect<ISideMenu, {}, ISideMenu, ISideMenuState>(mapStateToProps)(SideMenu);
You can use ISideMenu
as both your React component props and the object returned by mapStateToProps
. But in practice it might be best to create 2 separate interfaces.
In my apps, I usually can't be bothered typing the mapStateToProps
return object so I simply use:
connect<{}, {}, ISideMenu, ISideMenuState>(mapStateToProps)(SideMenu);