SolvedDefinitelyTyped @types/react-redux: Inferred wrong type in connect when component props is optional

  • I tried using the @types/xxxx package and had problems.
  • I tried using the latest stable version of tsc. https://www.npmjs.com/package/typescript
  • I have a question that is inappropriate for StackOverflow. (Please ask any appropriate questions there).
  • Mention the authors (see Definitions by: in index.d.ts) so they can respond.
    • Authors: @....

Mentioning @blakeembrey, @andy-ms, @alecmerdler πŸ˜‰

Code:

import React, { Component } from "react";
import { connect } from "react-redux";

import { RootState } from "@src/state/state";

interface Props {
    normal: string;
    optional?: number;
}
class TestComponent extends Component<Props> {
    render() {
        return <h1>Hello</h1>;
    };
}
function mapStateToProps(_state: RootState) {
    return { 
        normal: "test",
        optional: 5,
    };
}
const Connected = connect(mapStateToProps)(TestComponent);

Error:

[ts]
Argument of type 'typeof TestComponent' is not assignable to parameter of type 'ComponentType<{ normal: string; optional: number | undefined; } & DispatchProp<any>>'.
  Type 'typeof TestComponent' is not assignable to type 'StatelessComponent<{ normal: string; optional: number | undefined; } & DispatchProp<any>>'.
    Type 'typeof TestComponent' provides no match for the signature '(props: { normal: string; optional: number | undefined; } & DispatchProp<any> & { children?: ReactNode; }, context?: any): ReactElement<any> | null'.

The problem is when the optional prop is marked as optional - the TestComponent part of connect(mapStateToProps)(TestComponent); is reported red with above error code.

For unknown reasons the inferred type is StatelessComponent but TestComponent is a class.
When the prop is marked as optional: number|undefined all is ok, so it's a workaround for now.

32 Answers

βœ”οΈAccepted Answer

I hit this. You can fix easily enough with a cast.

function mapStateToProps(_state: RootState) {
    return { 
        normal: "test",
        optional: 5,
    } as Partial<Props>;
}

At least it works for me.

Other Answers:

Having the same issue here :/
Fun fact: it works normally with functional components, but not with class components.

@rafeememon
Unfortunately not 😞 I've installed 5.0.14 and the error is still here:
image
image
The problem is that connect types perform the type check from inverse side, so optional type a?: A is not assignable to type a: A|undefined.

And because of that, when your props are a: A, you can assign maybe undefined value to the a field and have no error at all, like photo: 1 !== 1 ? state.photos[0] : undefined πŸ™€

This is still an issue, a year later. Is there anyone that can identify and fix this issue?

Posting this here for feedback to this issue...

I hate declaring all Redux props as optional, so I opt to create two interfaces; Props & ReduxProps. That way when I implement the component, I don't get any extraneous properties showing (the optional redux-connect props). Problem is, when I use connect() I get a ts error that says

Argument of type 'typeof TestComponent' is not assignable to parameter of type 'ComponentType<never>'.
  Type 'typeof TestComponent' is not assignable to type 'ComponentClass<never, any>'.
    Types of property 'defaultProps' are incompatible.
      Type 'Partial<Props>' is not assignable to type 'never'.

In order to fix that issue and get sane code completion, I have resorted to casting the components. Here's what I've done, written as the TestComponent:

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

interface Props {
  normal: string
  optional?: number
  other: string
}

interface ReduxProps {
  isAuthenticated: boolean;
  username: string;
}
class TestComponent extends React.Component<Props & ReduxProps> {
  constructor(props: Props & ReduxProps) {
    super(props);
  }
  render() {
      return <h1>Hello{this.props.isAuthenticated ? ` ${this.props.username}` : ''}</h1>
  }
}
const mapState = state => ({
  isAuthenticated: state.auth.isAuthenticated,
  username: state.auth.username,
});

// Casting to prevent TS from throwing error output as above
const Connected = connect(
  mapStateToProps
)(
 TestComponent as React.ComponentClass<Props & ReduxProps> // cast the connect-input component with all props
) as React.ComponentClass<Props> // cast the final component just with Props

export default Connected

Now code completion will only show normal, other, optional?

I hate having to do the extra casting so if you have suggestions on why this is necessary please fire away.

More Issues: