reactjstypescriptreact-proptypes

Can I infer TypeScript type from a PropTypes shape?


I know how to infer types in this case:

import PropTypes from 'prop-types';

const props = {
  id: PropTypes.number,
};

type Props = PropTypes.InferProps<typeof props>;

const x: Props = {};
x.id; // number | null | undefined

However, in my case I have

const propsShape = PropTypes.shape({
  id: PropTypes.number,
  // more properties including nested PropTypes.shape calls
});

If I try to

type PropsFromShape = PropTypes.InferProps<typeof propsShape>;
const y: PropsFromShape = {};
const z = y.id;

it doesn't compile:

Type '{}' is not assignable to type 'PropsFromShape'.
  Property 'isRequired' is missing in type '{}' but required in type 'InferPropsInner<Pick<Requireable<InferProps<{ id: Requireable<number>; }>>, "isRequired">>'.

Property 'id' does not exist on type 'PropsFromShape'.

I could extract the argument of shape into a separate constant and work as above, but is there a nice way to infer the property types directly from propsShape?


Solution

  • To get the type of the nested object, you can use type NestedProps = PropTypes.InferProps<typeof propsShape>['isRequired'];

    import PropTypes from "prop-types";
    
    const propsShape = PropTypes.shape({
      nestedId: PropTypes.number,
      // more properties including nested PropTypes.shape calls
    });
    
    const props = {
      id: PropTypes.number,
      optionalWithShape: propsShape
    };
    
    type Props = PropTypes.InferProps<typeof props>;
    type NestedProps = PropTypes.InferProps<typeof propsShape>['isRequired'];
    
    const x: Props = {};
    x.id = 1;
    
    const y: NestedProps = {
      nestedId: 1
    }
    
    x.optionalWithShape = y;
    

    Alternatively, if you can have entire props definition in one place:

    import PropTypes from "prop-types";
    
    const props = {
      id: PropTypes.number,
      optionalWithShape: PropTypes.shape({
        nestedId: PropTypes.number
      })
    };
    
    type Props = PropTypes.InferProps<typeof props>;
    type NestedProps = Props['optionalWithShape'];
    
    const x: Props = {};
    x.id = 1;
    
    const y: NestedProps = {
      nestedId: 1
    }
    
    x.optionalWithShape = y;
    
    console.log(x.optionalWithShape.nestedId);
    

    The latter reads better IMHO.