'Preserve type argument in Redux connected component
I have a simple component that takes a type argument on props. When used, it infers the prop type and contextually types a callback param. However, when I wrap the component in connect()
, it loses the type argument inference and falls back to unknown
. Example:
import React from "react";
import { Component } from "react";
import { connect } from "react-redux"
type MyCompProps<T> = {
value: T;
onChange(value: T): void;
}
class MyComp<T> extends Component<MyCompProps<T>> { }
<MyComp value={ 25 } onChange={ val => {/* val will be contextually typed to `number` ✅ */} }/>
const stateToProps = (state: any) => ({ });
const dispatchToProps = { };
const MyContainer = connect(stateToProps, dispatchToProps)(MyComp);
<MyContainer value={ 25 } onChange={ val => {/* val is now `unknown` ❌ */} }/>
Is there any way to make MyContainer
behave like MyComp
? Either by passing in explicit type args to connect()
or by writing an assertion on the resulting type?
Solution 1:[1]
After a lot failed attempts to make connect()
behave as I wanted, I found the simplest solution was to just wrap the connected component in another component that re-exposed the type argument and renders the connected component:
const ConnectedMyComp = connect(stateToProps, dispatchToProps)(MyComp);
class MyContainer<T> extends Component<MyCompProps<T>> {
render() {
return <ConnectedMyComp { ...this.props }/>;
}
}
<MyContainer value={ 25 } onChange={ val => {/* val is `number` ? */} }/>
Full example that also deals with the whole dance around own props, state props, and dispatch props: Playground
Solution 2:[2]
There's two different answers here.
The first is that we show a technique for automatically inferring "the type of all props from Redux" in our React-Redux docs "Usage with TS" usage guide page, by using the ConnectedProps<T>
type and separating the connect
call into two pieces:
import { connect, ConnectedProps } from 'react-redux'
interface RootState {
isOn: boolean
}
const mapState = (state: RootState) => ({
isOn: state.isOn,
})
const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
}
const connector = connect(mapState, mapDispatch)
// The inferred type will look like:
// {isOn: boolean, toggleOn: () => void}
type PropsFromRedux = ConnectedProps<typeof connector>
That may help here, although tbh I really don't know if connect
will preserve a generic type like that.
That said, the better answer is:
use function components and the hooks API instead of class components and connect
!
The hooks API is simpler to use overall, and works much better with TypeScript. In addition, because you're no longer dealing with Higher-Order Components, there's no interference with whatever generic props types you might be using.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Aaron Beall |
Solution 2 | markerikson |