Amir Hoseinian

About

Utilize generics in typescript

Inferring react-redux connect prop types

A common problem before typescript 28 for React/Redux users was having to type manually all the props injected to a component by react-redux connect HOC.

In this article, we will explore how we can infer these types automatically using a couple of features introduces in typescript 28.

Two simple but important additions to typescript in version 28

  • Conditional types ↗︎ : T extends U ? X : Y
  • Return type: ReturnType<T>

mapDispatchToProps ↗︎

Can be either

  • Object
  • (State) => StateProps a function that returns the props
type MapDispath<T> = T extends Function ? ReturnType<T> : T;

Code above would return the return type of dispatch in case it is an Object and if not it will simply returns the Object type.


mapStateToProps ↗︎

can be either

  • (State) => StateProps a function that returns the props
  • (State) => () => StateProps a function that returns a function that returns the props
type MapStateToProps<T> = ReturnType<T> extends Function
  ? ReturnType<ReturnType<T>>
  : ReturnType<T>;

Here things are a bit more complex. mapState can either be a function that returns a function, in which case, we simply return the ReturnType of the ReturnType :). Or more simply it is a function that returns an Object (the state props), in which case, we will get the ReturnType of that function.


Connect them

We will name our generic type Connected like react-redux

Let us make a generic type which Combines the types from mapStateToProps & mapDispatchToProps

type Connected<S, D = undefined> = S extends Function
  ? D extends undefined
    ? MapStateToProps<S>
    : MapStateToProps<S> & MapDispatchToProps<D>
  : S;

Usage:

Connected<typeof mapState>
Connected<typeof mapDispatch>
Connected<typeof mapState, typeof mapDispatch>

Exmaple


type Props = OwnProps & Connected<typeof mapState, typeof mapDispatch>;

function ExampleComponent(props: Props){
  ...
}

const mapState(state) => ({ count: state.count });

const mapDispatch =  {
  increment: () => ({ type: "INCREMENT" }),
};

export default connect(mapState, mapDispatch)(ExampleComponent);

Please note that the techniques discussed above are not limited to connect example and are usable with a lot of APIs.