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.
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?
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 => (
Actually you don't need propTypes
here - labels
got just one nested field body
that is typeof FooterLabels
.
interface FooterProps {
labels: {
body: FooterLabels;
};
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With