How to use PropTypes.shape with Typescript
Asked Answered
G

3

10

I'm refactoring a React application from Javascript to Typescript but I'm having some troubles migrating especially the shape PropType. My code looks like this right now:

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

interface FooterProps {
  labels: FooterLabels;
}

interface FooterLabels {
  body: string;
}

const Footer: React.FC<FooterProps> = ({ labels }) => (
  <div className="footer">
    {/* ... */}
  </div>
);

Footer.propTypes = {
  labels: PropTypes.shape({
    body: PropTypes.string.isRequired
  }).isRequired
};

export default Footer;

But I'm getting an error in the PropTypes:

Type 'Validator<InferProps<{ body: Validator<string>; }>>' is not assignable to type 'Validator<FooterLabels>'.
  Type 'InferProps<{ body: Validator<string>; }>' is not assignable to type 'FooterLabels'.
    Property 'body' is optional in type 'InferProps<{ body: Validator<string>; }>' but required in type 'FooterLabels'.ts(2322)
FooterAppPromo.tsx(5, 3): The expected type comes from property 'labels' which is declared here on type 'WeakValidationMap<FooterProps>'

I'm new to Typescript so sometimes I don't really know what I'm doing, but I tried to do stuff like:

Footer.propTypes = {
  labels: PropTypes.shape<FooterLabels>({
    body: PropTypes.string.isRequired
  }).isRequired
};

But I'm getting the errors. I tried searching for example implementations but I couldn't find any.

EDIT

Typescript types and PropTypes are not the same thing. You can easily write an example where a Typescript validation passes but you still get a PropTypes warning (e.g. an external API that returns a number where should be a string). Here's two articles explaining why:

So my question is how can I make nested PropTypes objects (PropTypes.shape() or maybe PropTypes.objectOf()) work with TypeScript?

Gonnella answered 11/12, 2019 at 16:6 Comment(2)
I think only interface and React.FC<FooterProps> are enough. You don't need propTypes on React with TypescriptUvular
@AnhNguyen I would argue that if you do that, only TS-aware consumer of your component can be used. I'm also looking for a solution in TS with propTypes. Right now, tsc errors out with: Type 'undefined' is not assignable to type 'MetaType'.Perineum
F
9

The PropTypes error is because you are trying to use a PropType.shape, the shape allows you to have optional values on your shape, and in this case, your interface is strict, which means you do not have any kind of optional value, and React.Validator is trying to infer your types behind with your interfaces and that makes the error appear.

In simple words, if you have a strict interface with non-optional values, instead of using PropType.shape you should use PropType.exact and that should prevent the error.

E.G:

Footer.propTypes = {
  labels: PropTypes.exact({
    body: PropTypes.string
  }).isRequired
};

For that reason on your error message appears

Property 'body' is optional in type 'InferProps<{ body: Validator; }>' but required in type 'FooterLabels'.ts(2322)

EDITED

This happens only because you are using on the component const Footer: React.FC<FooterProps> = ({ labels }) => (

The FC ( FunctionComponent Interface ) looking into the interfaces is presented as

interface FunctionComponent<P = {}> {
        (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
        propTypes?: WeakValidationMap<P>;
        contextTypes?: ValidationMap<any>;
        defaultProps?: Partial<P>;
        displayName?: string;
    }

The line that we should highlight here is the propTypes?: WeakValidationMap<P>; This is the one that infers our types based on our PropTypes.

You have a second option to use types on React, but I do not recommend it, instead of using FC, you can use ReactNode type, but you will lose the inferences that you have on the FC interface type inferring.

E.G. function Footer = ({ labels }:FooterProps): ReactNode => (

Fabricate answered 2/8, 2020 at 18:51 Comment(0)
L
1

Actually you don't need propTypes here - labels got just one nested field body that is typeof FooterLabels.

interface FooterProps {
  labels: {
     body: FooterLabels;
  };
}
Logarithm answered 11/12, 2019 at 16:14 Comment(0)
M
-2
interface FooterProps {
  labels: PropTypes.InferProps<{ body: string }>
}
Manslayer answered 9/4, 2020 at 8:41 Comment(1)
Please don't post only code as an answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually of higher quality, and are more likely to attract upvotes.Ellingson

© 2022 - 2024 — McMap. All rights reserved.