SolvedDefinitelyTyped @types/react-redux broken on typescript >= 2.3

Even a very basic use of connect on a React component ends in type errors. Reduced example:

import * as React from 'react';
import {connect, DispatchProp} from 'react-redux';

interface Props {
    thing: string;
}

const mapStateToProps = (state) => ({
    thing: 'abc'
});

@connect(mapStateToProps)
class Test extends React.Component<Props & DispatchProp<any>, void> {
    render() {
        return (<div>{this.props.thing}</div>);
    }
}

On typescript 2.2.2 and below that type checks fine, but move to 2.3 or 2.4 and it results in cryptic error:

Argument of type 'typeof Test' is not assignable to parameter of type 'Component<DispatchProp<any> & { thing: string; }>'.
  Type 'typeof Test' is not assignable to type 'StatelessComponent<DispatchProp<any> & { thing: string; }>'.
    Type 'typeof Test' provides no match for the signature '(props: DispatchProp<any> & { thing: string; } & { children?: ReactNode; }, context?: any): ReactElement<any>'.

This is with @types/react-redux@4.4.45 and @types/react@15.0.35

30 Answers

✔️Accepted Answer

I could only get this working by splitting everything out, its a bit verbose but there are types for everything I guess!

export interface IMergedProps {
    aProp: string;
 }

export interface IStateProps {
}

export interface IDipatchProps {
     load?: (ref: string) => void;
 }

export interface IState {

}

class Viewer extends React.Component<IMergedProps & IStateProps & IDipatchProps, IState> {
    constructor(props: IMergedProps, context?: any) {
        super(props, context);
    }

    componentDidMount() {
        if (this.props.load) {
            this.props.load(this.props.aProp);
        }
    }

    render() {

     return (
               <div  />
             );
    }
}

function mapStateToProps(state: IRootState): IStateProps {
    return {
      
    };
}
const mapDispatchToProps = (dispatch: Dispatch<any>): IDipatchProps => {
    return {
        load: (aProp: string) => { dispatch(actionCreators.search(aProp)); },
    };
};

export const ConnectedViewer = connect<IStateProps, IDipatchProps, IMergedProps, IRootState>(
   mapStateToProps,
   mapDispatchToProps
)(Viewer);

Other Answers:

I ran into this issue yesterday and found what appears to be the solution. You need to include the dispatch method and any mapped props added through the connect method to the props interface on your component. They also can not be optional properties.

I used to write components like this below which now gives the errors in this thread. You would get similar errors if you omit the dispatch or mapped props.

export interface AppProps {
  dispatch?: (action: any) => void;
  appStateThing?: StatePart;
}

class App extends React.Component<AppProps, {}> {
  render() {
    return <div>I am an App</div>;
  }
}

export default connect(s => {
  return {
    appStateThing: s.someStatePart,
  };
})(App);

....

<Provider state={state}>
  <App />
</Provider>

The solution is to change your props interface.

export interface AppProps {
  dispatch: (action: any) => void;
  appStateThing: StatePart;
}

fwiw, I get a slightly more useful error by omitting the second generic type parameter:

- class Test extends React.Component<Props & DispatchProp<any>, void> {
+ class Test extends React.Component<Props & DispatchProp<any>> {

(I believe it's defaulted to {} for state.)

The new error:

Unable to resolve signature of class decorator when called as an expression.
  Type 'ComponentClass<{}>' is not assignable to type 'typeof Test'.
    Type 'Component<{}, ComponentState>' is not assignable to type 'Test'.
      Types of property 'render' are incompatible.
        Type '() => false | Element' is not assignable to type '() => Element'.
          Type 'false | Element' is not assignable to type 'Element'.
            Type 'false' is not assignable to type 'Element'.

I came across this problem in my code and after playing around found that making the props non-optional fixed the problem, just as codecorsair found.

But the props marked as optional could be undefined, so making them non-optional makes the typings incorrect, which is a problem in of itself.

I ended up with a solution similar to AndyBan.
It isn't quite as verbose, but it's still sub-optimal.

//Component.tsx
export interface StateProps {
    returnPath: string;
    user?: User;
}

export interface DispatchProps {
    loadUser: (userId: string) => any;
}

export interface Props extends StateProps, DispatchProps { }

export class Component extends React.Component<Props> {
    ...
}
//Container.tsx
export const Container = ReactRedux.connect(
    (state: Store.ApplicationState, ownProps: OwnProps): Component.StateProps => ({
        returnPath: ownProps.returnPath,
        user: undefined,
    }),
    (dispatch: Store.Dispatch) => Redux.bindActionCreators({
        loadUser: Store.User.actionCreators.loadUser
    }, dispatch)
)(Component.Component);

Splitting out the Props adds a little bit of extra code in the component, and then just one extra type annotation in the Container.

You don't have to split things out like this unless you encounter this particular issue, and it maintains the original Props type so any existing code should be unaffected.

I found the same error when using static getDerivedStateFromProps(...) - I was only returning state inside an if() check - putting a return {}; outside of that fixed the error (hope this is useful to someone).

More Issues: