Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding children prop in React with Typescript 3.5+

I have a case where I want to explicitly scope the child component of a React.FC to be of a specific type, overriding the default which is: React.ReactNode.

The purpose is that I'm creating a little library, and I want to have compile-time errors which will tell the user that he can only use a specific component type as a child for the parent.

An OK example would be:

<MainComponent>
    <ChildComponent>
</MainComponent>

An FAILED example would be:

<MainComponent>
    <Route ...>
    <ChildComponent>
</MainComponent>
  • This should fail as Route component is not ChildComponent and fails the check.

Default children property in React.FC is derived from:

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

type PropsWithChildren<P> = P & { children?: ReactNode };

What I have tried is something like this:

Omit<React.FC, 'children'> & {
  children: ChildComponent | ChildComponent[];
}

However I can either get only React.ReactNode or an union of my children and React.ReactNode.

I can't seem to figure out how to overwrite the prop to only explicitly use my type for this prop.

I'm using Typescript 3.5.3 and Omit / Extend ... that now became the part of standard TS library.

like image 238
Vlatko Vlahek Avatar asked Jul 19 '19 20:07

Vlatko Vlahek


1 Answers

No this is not possible.

<MainComponent>
    <ChildComponent/>
</MainComponent>

is JSX syntax and will get compiled to

React.createElement(MainComponent, null, React.createElement(ChildComponent, null));

And then we have to look at how typescript handles these JSX elements. This is clearly documented in their handbook:

The JSX result type

By default the result of a JSX expression is typed as any. You can customize the type by specifying the JSX.Element interface. However, it is not possible to retrieve type information about the element, attributes or children of the JSX from this interface. It is a black box.

You can override children to only be string or function (to realize something like the render prop pattern) but any custom component will get casted to JSX.Element and this is a blackbox as stated in the documentation.

There is currently an issue open for this, in which a roadmap how JSX.Element can be generalized to support your use case, but without any clear timeline when and to which extent or if it will land.

like image 135
ChrisG Avatar answered Oct 11 '22 05:10

ChrisG