Redux-connected React components in TypeScript

When I originally started using React together with TypeScript, I felt it was a great marriage of technologies. However, adding Redux into the mix (which is awesome, BTW) has created a lot of complexity to properly type my React Components. Type inference doesn't appear to work smoothly for the connect method in React Redux.

For simple stateless components, there is a pretty good solution by Silvio J. Gutierrez, which is pretty similar to something I had been using.

I've tried to come up with a good solution for more complex components as well. This is the best I've managed so far:

interface OwnProps {
  componentOwnProp: string;
}

function mapStateToProps(state: State) {
  return {
    someProperty: state.someProperty
  };
}

function mapDispatchToProps(dispatch) {
   // ...
}

// Magic happening here
const { propsGeneric, connect } =
    connectedComponentHelper<OwnProps>()(mapStateToProps, mapDispatchToProps);
type ComponentProps = typeof propsGeneric;

// Use the inferred ComponentProps type as generic type parameter
class MyComponent extends React.Component<ComponentProps, {}> {
  // ...
}

// Export the connected component
export default connect(MyComponent);

Wait... What?

The syntax is a bit confusing; the connectedComponentHelper higher-order function returns a function which is immediately called with the mapStateToProps and mapDispatchToProps functions as parameters*. This returns two things:

  • A dummy value of the type to be used for the P generic type parameter when extending React.Component<P, S>. The return types of mapStateToProps and mapDispatchToProps are inferred by the compiler and the helper function returns the "type" that combines the components own props with the state and the dispatch props.
    Unfortunately since types don't exist at runtime, you still have to use typeof to define the type alias to use.
  • A function that can be used to connect your component to the Redux store easily.

Also, take note that the connectedComponentHelper function itself is generic and requires you to pass in the type for the component's own properties.

*: Yep, that means that function is actually also higher-order. Oh boy...

The Helper Function

Here's the implementation of the helper function, which can of course be reused for all your connected components:

import { connect } from 'react-redux';

interface Func<T> {
  ([...args]: any): T;
}

export function connectedComponentHelper<TProps>() {
  return <TStateProps, TDispatchProps>(
    mapStateToProps: Func<TStateProps>,
    mapDispatchToProps?: Func<TDispatchProps>) => (
    {
      propsGeneric: null as TProps & TStateProps & TDispatchProps,
      connect: (component) => connect(mapStateToProps, mapDispatchToProps)(
        component) as any as React.ComponentClass<TProps>
    }); 
}

Wrapping Up

So, that code was definitely more complex than I would have liked it to be. Still, it beats manually defining the StateProps and DispatchProps types, plus the intersection type OwnProps & StateProps & DispatchProps, only to spend 15 minutes getting the call to connect to compile properly before chickening out and casting to any.

What do you think of this solution? Have you come up with something better?

Add comment

Loading