Typescript Tips Series: Proper Typing of react-redux Connected Components

Alex Heitzmann
Knerd
Published in
3 min readJan 16, 2018

Knewton has been incrementally converting its front-end React and backend Node.js codebases to TypeScript. In this series we highlight pitfalls we have encountered along the way, lessons we’ve learned, and best practices that we’ve developed.

react + react-router + typescript

For our first tip of this series, let’s look at Knewton’s preferred approach to defining the typescript types for a react-redux connected component. As a component author, it’s important to correctly specify your component’s property types so that users of your component reap the full benefits of type checking and aren’t mislead into using your component incorrectly. Doing this is fairly straightforward with a regular React component, but gets a bit more complicated when react-redux is introduced to the picture. If you’re unfamiliar with React, Redux, or react-redux, you’ll want to take a few minutes to read through the the linked docs before continuing on.

Ok then! Without further ado, here is a template showcasing a fully typed component wrapped with react-redux’s connect() higher-order-component:

Future readers, note that this pattern is tested with TypeScript 2.6, React 16.2, and react-redux 5.0.

Explanation

In this example, MyComponent is our wrapped component. The props it receives are the union of three sets of props (represented by the Props intersection type), which are combined by the connect() method.

  • StateProps: The type for the props provided by mapStateToProps()
  • DispatchProps: The type for the props provided by mapDispatchToProps()
  • OwnProps: The type for the props provided by the parent component

The StateProps and DispatchProps types are nice to have, as they help to catch errors in the mapStateToProps and mapDispatchToProps methods at compile time. More important is the OwnProps type, which defines the set of properties available on the redux-connected component that is exported from the module. OwnProps is essentially the public interface of our component, so is the only one of the types that is exported from our module.

The State type represents the internal React component state of MyComponent. This is also defined purely to provide some compile-time checks for the component authors, as the component’s state is private to the component. If your component doesn’t use state, you could omit the State type and define your wrapped component like so:

const MyComponent: React.SFC<Props> = (props: Props) => {
...
}

Further Considerations

Simplifying

It is perfectly legitimate to omit any of the types that are not used. For instance, if your connected component does not need to dispatch any redux actions, and doesn’t need a custom combiner to merge the StateProps, DispatchProps, and OwnProps, you can write the connect() call as follows, and not bother to define a DispatchProps interface.

connect<StateProps, {}, OwnProps>(mapStateToProps)(MyComponent)

Alternate Naming

You may also decide to name your types a bit more descriptively — for example MyComponentStateProps instead of just StateProps. We found this to be unnecessary because we strictly enforce a “one component per module” rule, and in the rare case when another component needs to import a component’s OwnProps they are free to rename the imported type to resolve any ambiguity:

import { OwnProps as MyComponentProps } from './my-component'

Namespacing

One interesting variant that we’ve explored is putting the prop types in a namespace, effectively extending the wrapped component through declaration merging.

namespace MyComponent {
export interface OwnProps { }
// etc...

type Props = StateProps & DispatchProps & OwnProps
}export class MyComponent extends React.Component<
MyComponent.Props, MyComponent.State> {
...
}
export default connect...

Ultimately we decided against this pattern for a couple of reasons:

  1. It is more verbose for the component author.
  2. It forces all of the types to be exported so that they can be used in the component definition.
  3. It’s only useful when importing the wrapped component directly. Generally we would only export the wrapped component for testing — all production code would be expected to use the redux-connected component that is the module’s default export.

Conclusion

By following the template above you can save yourself a lot of trouble debugging type issues. It may seem like an obvious setup for experienced typescript users, but the react-redux typings can be quite daunting for the uninitiated, so we hope this is helpful.

If you like our template and want to use it in your own projects, consider checking out this vscode snippet to save some time on the keyboard.

Comments and suggestions are welcome!

--

--