Definitelytyped: [prop-types] Prop types are required for SFCs (incorrect), but not for classes (correct)

Created on 21 Aug 2018  路  8Comments  路  Source: DefinitelyTyped/DefinitelyTyped

@DovydasNavickas @ferdaber

Recent changes to @types/react and @types/prop-types have forced all propTypes to be defined, or none at all. This change was made when the ValidationMap in React was changed to PropTypes.ValidationMap. React defined the properties as optional (https://github.com/DefinitelyTyped/DefinitelyTyped/commit/1d9679b2dcf7c9643d623b626c0820f84eaa8d7e#diff-96b72df8b13a8a590e4f160cbc51f40cL2214) while PropTypes force removes the optional (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/prop-types/index.d.ts#L27).

React doesn't require prop types for every prop, nor does it require prop types at all. We should be following React's API, not what we think is best. That being said, I've noticed some weirdness when using these latest versions, specifically that SFCs with the same prop types fail, while classes with the same prop types do not. From testing, either all props are required, or no props, when partial props should be allowed.

For example the SFC:

import React from 'react';
import PropTypes from 'prop-types';

interface TestProps {
  foo: string;
  bar?: number;
}

const Test: React.SFC<TestProps> = () => null;

Test.propTypes = {
  foo: PropTypes.string.isRequired,
};

Test.defaultProps = {
  bar: 123,
};

Fails with the error:

src/Test.ts:13:1 - error TS2322: Type '{ foo: Validator<string>; }' is not assignable to type 'ValidationMap<TestProps>'.
  Property 'bar' is missing in type '{ foo: Validator<string>; }'.

13 Test.propTypes = {
   ~~~~~~~~~~~~~~

While a class with the same types does not error.

import React from 'react';
import PropTypes from 'prop-types';

interface TestProps {
  foo: string;
  bar?: number;
}

class Test extends React.Component<TestProps> {
  static propTypes = {
    foo: PropTypes.string.isRequired,
  };

  static defaultProps = {
    bar: 123,
  };

  render() {
    return null;
  }
}

I've done some digging, but I have yet to find any issues with the current definitions.

Most helpful comment

Hitting this issue with class components as well, when passed into an HOC / assigned to React.ComponentType

// [email protected]
// @types/[email protected]
// @types/[email protected]

import React from 'react';
import PropTypes from 'prop-types';

type Props = {
  someProp: boolean;
  someOtherProp: string;
};

class MyComponent extends React.Component<Props> {
  static propTypes = {
    someProp: PropTypes.bool.isRequired,
  };

  render() {
    return <div />;
  }
}

<MyComponent someProp someOtherProp="" />;

// ---

function withHoc<P>(Component: React.ComponentType<P>) {
  class WithHoc extends React.Component<P> {
    render() {
      return <Component {...this.props} />;
    }
  }
  return WithHoc;
}

// error TS2345: Argument of type 'typeof MyComponent' is not assignable to parameter of type 'ComponentType<Props>'.
//   Type 'typeof MyComponent' is not assignable to type 'ComponentClass<Props, any>'.
//     Types of property 'propTypes' are incompatible.
//       Type '{ someProp: Validator<boolean>; }' is not assignable to type 'ValidationMap<Props>'.
//         Property 'someOtherProp' is missing in type '{ someProp: Validator<boolean>; }'.
const MyComponentWithHoc = withHoc(MyComponent);

All 8 comments

I don't see how ComponentClass (type where propTypes are defined) are associated with the Component class.

The errors arise on classes when being passed to an HOC.

For example, our Button component being styled.

class Button extends React.Component<ButtonProps & WithStylesProps>  {
  static propTypes = {
    large: sizingProp,
    small: sizingProp,
  };

  // ...
}

export default withStyles()(Button);

Errors with:

src/components/Button/index.tsx:163:3 - error TS2345: Argument of type 'typeof Button' is not assignable to parameter of type 'ComponentType<ButtonProps & WithStylesProps>'.
  Type 'typeof Button' is not assignable to type 'StatelessComponent<ButtonProps & WithStylesProps>'.
    Types of property 'propTypes' are incompatible.
      Type '{ large: Requireable<boolean>; small: Requireable<boolean>; }' is not assignable to type 'ValidationMap<ButtonProps & WithStylesProps>'.
        Property 'block' is missing in type '{ large: Requireable<boolean>; small: Requireable<boolean>; }'.

163 )(Button);
      ~~~~~~

And our HOC definition:

export default function withStyles<TP extends {} = {}>(
  callback: StyleDeclaration | ((theme: Theme, props: TP) => StyleDeclaration),
  options?: any,
): <P extends {} = {}>(
  WrappedComponent: React.ComponentType<P & WithStylesProps>,
) => React.ComponentType<P & WithStylesWrapperProps> {
  return style(callback, options);
}

This is causing a ton of issues I can't seem to resolve. Had to drop down to 16.4.7 so we can move forward.

There was supposed to be a PR opened to remove the -? modifier to ValidationMap, but that would probably resolve the issue. Though I'm not sure if it will preserve prop inference for library managed attributes for nested prop types.

I tried replacing -? with ?, but a ton of other errors arose. Seems like a very involved problem to fix, but would most definitely be a welcome one.

Hitting this issue with class components as well, when passed into an HOC / assigned to React.ComponentType

// [email protected]
// @types/[email protected]
// @types/[email protected]

import React from 'react';
import PropTypes from 'prop-types';

type Props = {
  someProp: boolean;
  someOtherProp: string;
};

class MyComponent extends React.Component<Props> {
  static propTypes = {
    someProp: PropTypes.bool.isRequired,
  };

  render() {
    return <div />;
  }
}

<MyComponent someProp someOtherProp="" />;

// ---

function withHoc<P>(Component: React.ComponentType<P>) {
  class WithHoc extends React.Component<P> {
    render() {
      return <Component {...this.props} />;
    }
  }
  return WithHoc;
}

// error TS2345: Argument of type 'typeof MyComponent' is not assignable to parameter of type 'ComponentType<Props>'.
//   Type 'typeof MyComponent' is not assignable to type 'ComponentClass<Props, any>'.
//     Types of property 'propTypes' are incompatible.
//       Type '{ someProp: Validator<boolean>; }' is not assignable to type 'ValidationMap<Props>'.
//         Property 'someOtherProp' is missing in type '{ someProp: Validator<boolean>; }'.
const MyComponentWithHoc = withHoc(MyComponent);

Ah dependency hell! We ensure all our dependencies have no carets to prevent updates etc:
"@types/react": "16.4.7",

However the package.json files in those dependencies did not have carats removed. So whenever yarn install was run again something deep in the dependency list would update @types/react to 16.4.14, which would cause hundreds of build errors.

When using yarn, add a "resolutions" section In package.json, at the same level as dependencies. I added this which forces all other dependencies to use the same react version:

"resolutions": {
"@types/react": "16.4.7",
"@types/prop-types": "15.5.5",
}

Works for yarn, not sure if that works with npm though.

Was this page helpful?
0 / 5 - 0 ratings