Why does a generic React component with a default render-prop fail PropTypes validation?

54 Views Asked by At

I am trying to create a React component that has a render-prop with a generic type. To make the component easier to use I also want to supply a default value for the render-prop. The code works as expected. However, when I type-check the code I get the following warning about conflicts between the PropTypes and Typescript specifications. Can anyone explain why I'm getting this error or how to fix it?

src/test.tsx:19:3 - error TS2322: Type '{ ({ data }: TChildProps<TData>): JSX.Element; propTypes: { data: PropTypes.Validator<NonNullable<PropTypes.InferProps<{ id: PropTypes.Validator<string>; }>>>; }; }' is not assignable to type 'FunctionComponent<TChildProps<V>>'.
  The types of 'propTypes.data' are incompatible between these types.
    Type 'Validator<NonNullable<InferProps<{ id: Validator<string>; }>>>' is not assignable to type 'null extends V ? Validator<V | (V & null) | undefined> : undefined extends V ? Validator<V | (V & undefined) | null> : Validator<V>'.

19   Child = DefaultChild,

Here is a simplified version of my code that produces the error:

import PropTypes from "prop-types";

export interface TData {
  id: string;
}

export interface TChildProps<V extends TData> {
  data: V;
}

export interface TParentProps<V extends TData> {
  data: V;
  Child?: React.ComponentType<TChildProps<V>>; // Causes PropTypes validation error.
}

export const Parent = <V extends TData>({
  data,
  Child = DefaultChild,
}: TParentProps<V>) => {
  return <Child data={data}></Child>;
};

export const DefaultChild = ({ data }: TChildProps<TData>) => {
  return <p>{data.id}</p>;
};

DefaultChild.propTypes = {
  data: PropTypes.shape({
    id: PropTypes.string.isRequired,
  }).isRequired,
};

Update

It's not a perfect solution, but it is possible to disable the error by explicitly setting the type of DefaultChild.propTypes to object:

DefaultChild.propTypes = {
  data: PropTypes.shape({
    id: PropTypes.string.isRequired,
  }).isRequired,
} as object;
1

There are 1 best solutions below

1
Ahmed Sbai On

In your code, the problem is how the propTypes of DefaultChild are defined, TypeScript's PropTypes validator has different behavior based on the type of the generic it receives, so here is the conflict.

More often than not we might be working on an existing React application, where a team has decided to introduce TypeScript. This would also mostly mean that if Components props have to be defined at some point, either defining all at once or gradually. In some cases there also might be existing prop-type definitions. Instead of removing the existing prop-type definitions, we might be able to build a bridge between these prop-types and TypeScript.

you can have a look at this article it introduces how to use PropTypes.InferProps