Definitelytyped: [@types/prop-types and @types/react] PropTypes.shape throws type errors and incorrectly insists on PropTypes.exact

Created on 12 Apr 2019  路  16Comments  路  Source: DefinitelyTyped/DefinitelyTyped

If you know how to fix the issue, make a pull request instead.

  • [x] I tried using the @types/react and @types/prop-types packages and had problems.
  • [x] I tried using the latest stable version of tsc. https://www.npmjs.com/package/typescript
  • [x] I have a question that is inappropriate for StackOverflow. (Please ask any appropriate questions there).
  • [x] [Mention](https://github.com/blog/821-mention-somebody-they-re-notified) the authors (see Definitions by: in index.d.ts) so they can respond.

    • Authors: @Asana, @AssureSign, @Microsoft, @johnnyreilly, @bbenezech, @pzavolinsky, @digiguru, @ericanderson, @tkrotoff, @DovydasNavickas, @onigoetz, @theruther4d, @guilhermehubner, @ferdaber, @jrakotoharisoa, @pascaloliv, @hotell, @franklixuefei, @Jessidhia, @pshrmn, @saranshkataria, @lukyth, @eps1lon (Reporter's note, sorry for this long list of author notifications. I feel like I'm alerting half of github.)

If you do not mention the authors the issue will be ignored.

I think there's be a bug in the typing for PropTypes.shape. (Stick with me here, because it's a little bit involved, but I'll have a minimal example at the end to demonstrate.) When a component that uses PropTypes.shape in its prop types for a nested object is passed into a higher-order component, the Typescript compiler throws an error saying that the type of the component doesn't match what was expected given how the prop types describe it. If I switch to using PropTypes.exact, that error goes away. However, I understand the difference between the two and I _want_ to use PropTypes.shape. I want the interface inside my props to be "open". I don't mind if there are extra members in the object so long as it has the members I care about.

Here's my example:

import React from "react";
import * as PropTypes from "prop-types";
import { withNetwork } from "react-fns";

interface Stuff {
  one: number;
  two: number;
}

interface Thing {
  stuff: Stuff;
}

function Component({ stuff }: Thing) {
  return (
    <div>
      <span>{stuff.one}</span>
      <span>{stuff.two}</span>
    </div>
  );
}

Component.propTypes = {
  stuff: PropTypes.shape({
    one: PropTypes.number.isRequired,
    two: PropTypes.number.isRequired
  }).isRequired
};

const wrapped = withNetwork(Component);

I'm using the react-fns library because it has higher-order components in it and it was the first library with them that I could find on short notice to demonstrate this. I don't believe the actual higher-order component makes a difference.

In this example, the Typescript compiler will report the following error on the last line.

Argument of type 'typeof Component' is not assignable to parameter of type 'ComponentType<Thing & NetworkProps>'.
  Type 'typeof Component' is not assignable to type 'FunctionComponent<Thing & NetworkProps>'.
    Types of property 'propTypes' are incompatible.
      Type '{ stuff: Validator<InferProps<{ one: Validator<number>; two: Validator<number>; }>>; }' is not assignable to type 'WeakValidationMap<Thing & NetworkProps>'.
        Types of property 'stuff' are incompatible.
          Type 'Validator<InferProps<{ one: Validator<number>; two: Validator<number>; }>>' is not assignable to type 'Validator<Stuff>'.
            Type 'InferProps<{ one: Validator<number>; two: Validator<number>; }>' is not assignable to type 'Stuff'.
              Property 'one' is optional in type 'InferProps<{ one: Validator<number>; two: Validator<number>; }>' but required in type 'Stuff'.ts(2345)

It's reporting that the property one is optional. That's not true. Note that if you switch from PropTypes.shape to PropTypes.exact, this error is no longer reported. However, that's not my intention and it changes the meaning and gives runtime errors when stuff (in this case) has more members than just one and two.

Most helpful comment

Awesome, I'll take a look. Thanks!

All 16 comments

Here's my example in codesandbox.io if you want to hit the ground running. https://codesandbox.io/s/5ww2o4mn74

Yeah that's very odd, I wonder if it's a recent regression introduced by a types change or a TypeScript change. Which version of TS are you using?

I'm using Typescript 3.4.3, the latest version. So this could be a recent regression.

Can you double check that this same thing is happening for lower versions of TS?

Gladly. How low should I go?

I would want to see down to TS 3.0.

I'll try to find some time today to test it out.

Sorry for the wait. I didn't get around to testing this out until today.

I don't think this is a regression related to Typescript itself. I've tried 3.4.3, 3.0.3, and even 2.9.2 and they've all given me that same error message. I'm going to try prop-types and its associated typescript types to see if a change in the types caused this at some point.

From my testing, it appears the regression happened in this library between versions 15.5.9 and 15.7.0. That is, the code compiles without errors or warnings using @types/prop-types version 15.5.9.

Here's my minimum reproducible example repository. https://github.com/JayAndCatchFire/proptypes-regression

Awesome, I'll take a look. Thanks!

Oh, I'm using yarn instead of npm in my workflow. You may have to make a couple of small edits to my makefile if you prefer npm. In fact, I'm going to make npm/yarn a variable in the makefile to make it even easier to switch.

Using npm/yarn doesn't affect this bug. It's just a personal preference.

EDIT: Done! Switching between yarn and npm is a one-line change on the second line of the Makefile: https://github.com/JayAndCatchFire/proptypes-regression/blob/e913cb1a4acb8ef931685e0e9fee3f64795b64f9/Makefile#L2

I'm having a similar issue (truncated):

const AppPropTypes = {
  previewProduct: PropTypes.shape({
    bsin: PropTypes.string.isRequired,
  }),
};

type AppProps = PropTypes.InferProps<typeof AppPropTypes>;
type ProductType = {
  bsin: string,
};
 Overload 1 of 2, '(props: Readonly<P>): ProductPreview', gave the following error.
    Type 'InferProps<{ bsin: Validator<string>; }>' is not assignable to type 'ProductType'.
      Property 'bsin' is optional in type 'InferProps<{ bsin: Validator<string>; }>' but required in type 'ProductType'.
  Overload 2 of 2, '(props: P, context?: any): ProductPreview', gave the following error.
    Type 'InferProps<{ bsin: Validator<string>; }>' is not assignable to type 'ProductType'.

I also found that switching from shape to exact stopped the errors, but I'm yet to determine if that's a viable solution.

packages used:

  "devDependencies": {
    "@types/node": "^12.7.5",
    "@types/prop-types": "^15.7.2",
    "@types/react": "^16.9.2",
    "@typescript-eslint/eslint-plugin": "^2.3.0",
    "@typescript-eslint/parser": "^2.3.0",
    "babel-core": "7.0.0-bridge.0",
    "babel-jest": "24.9.0",
    "css-loader": "3.2.0",
    "eslint": "^6.4.0",
    "eslint-config-airbnb": "^18.0.1",
    "eslint-plugin-import": "^2.18.2",
    "eslint-plugin-jest": "^22.17.0",
    "eslint-plugin-jsx-a11y": "^6.2.3",
    "eslint-plugin-react": "^7.14.3",
    "eslint-plugin-react-hooks": "^2.0.1",
    "jest": "24.9.0",
    "react-test-renderer": "16.9.0",
    "style-loader": "1.0.0",
    "typescript": "^3.6.3"
  },

Yea I have found this error as well, using PropTypes.InferProps rather than just inserting it directly as above:

  • shape is permissive of extra key/value pairs as a PropType but will not allow this in TS
  • exact will not let you add extra key/value pair as a PropType but requires every key in Typescript, even those marked as optional

Not sure if this is a bug in PropTypes or here but it certainly makes it difficult to use the two systems side by side.

Can produce a small example if that would help.

I have a similar error

// types.ts
export type AvatarProps = {
    src?: string;
    initials?: string;
    icon?: ReactNode;
};

// component.tsx
interface AvatarGroupProps extends BaseProps {
    size?: AvatarSizes;
    avatars?: Array<AvatarProps>;
    maxAvatars?: number;
    showCounter?: boolean;
    style?: object;
}

/**
 * An AvatarGroup is an element that communicates to the user
 *  that there are many entities associated to an item.
 */

const AvatarGroup: React.FC<AvatarGroupProps> = ({
    size,
    avatars = [],
    maxAvatars,
    showCounter,
    style,
}: AvatarGroupProps): JSX.Element => {
avatars: PropTypes.arrayOf(
        PropTypes.shape({
            src: PropTypes.string,
            initials: PropTypes.string,
            icon: PropTypes.node,
        }),
    ),

image

Was this page helpful?
0 / 5 - 0 ratings