Typescript react children interface

Understanding React Children types

So, here’s the deal. I’m not a big fan of React’s children property. Don’t get me wrong, I know why it’s good. I know why it’s useful and I also very much know that I don’t like to use it much when I’m writing React components. I’ve seen it used mostly to create stateless wrappers that only add an extra, non-semantic div and a CSS class, resulting in the ever wonderful:

Wrapper> HeaderWrapper> p>Somethingp> HeaderWrapper> Wrapper> 
 class="container">  class="header-container"> Something   

This is a simplified example. what I’m trying to say is that my experience with components that make explicit use of children is bad. And I’m biased.

But when Felipe showed me his idea for a component that used children, not just for adding a wrapper, but for making decisions on which child to render based on parent props, I realized I should probably put my bias aside. And this is when we asked ourselves the question to end all questions:

Setting out

We started where every journey starts. Five steps further than we should have, by trying to immediately run something on a .tsx file that looked like this:

interface ChildComponentProps  a: number; b: string; > interface ParentComponentProps  children: React.ReactElementChildComponentProps>[]; > const ChildComponent: React.FCChildComponentProps> = ( a, b >) => ( p> a> b> /p> ); const ParentComponent: React.FCParentComponentProps> = ( children >) => ( <>children>/> ); 

It seemed like we had triumphed! We had no red squiggly lines on our code and the idea looked sound. So, we tried it out:

const Usage = () => ( ParentComponent> ChildComponent a=1> b="First Child" /> ChildComponent a=2> b="Second Child" /> /ParentComponent> ); 

This works fine. But we needed to make sure that Typescript would yell at us if we tried to give a child that wasn’t a ChildComponent . And we hit a concrete wall:

const Usage = () => ( ParentComponent> ChildComponent a=1> b="First Child" /> ChildComponent a=2> b="Second Child" /> p>I'm not a ChildComponent, this shouldn't work/p> /ParentComponent> ); 

Why it worked (when it shouldn’t have)

There’s a very simple reason why our component did not yell at us when we passed it a child that didn’t fulfill the constrain we thought we had in place. And it has to do with the type of a FunctionComponent in React. Here we go: FunctionComponent is:

interface FunctionComponentP = <>>  (props: PropsWithChildrenP>, context?: any): ReactElementany, any> | null; propTypes?: WeakValidationMapP>; contextTypes?: ValidationMapany>; defaultProps?: PartialP>; displayName?: string; > 

We’re interested in the first line of that interface definition, the one where the function that takes props is defined. So, we dive a bit deeper into what PropsWithChildren

is and find this:

type PropsWithChildrenP> = P &  children?: ReactNode >; 

This is it. This is the aha moment. Or maybe it should’ve been, if we already knew how Typescript handles these cases, which we didn’t at the time. What we have here is a type extended by an intersection, where both sides of the intersection have differing definitions of a property with the same name. Remember, our P in this case was:

interface ParentComponentProps  children: React.ReactElementChildComponentProps>[]; > 

See how both the P and the inline type < children?: ReactNode>have the children property? And furthermore, they have different values! So, how does Typescript resolve extended types where this happens? Well, it does the only thing that makes sense. It creates an union type out of them. What comes out after all this is done is:

interface FinalParentComponentProps  children: React.ReactelementChildComponentProps>[] | ReactNode; > // This is ReactNode btw: type ReactNode = | ReactChild | ReactFragment | ReactPortal | boolean | null | undefined; // And this is ReactChild type ReactChild = ReactElement | ReactText; 

And that’s it. ReactElement is fulfilled by any JSX element, like our

Not correct component

intruder up there. And this makes sense.

The React contract

Apart from any internal React explanation (there is one, but now is not the place), at the type definitions perspective, this makes sense. React’s component contract is that they will render the JSX passed into HTML. And HTML will let us pass

s or anything else, inside anything really. Sure, sometimes it might yell at us for violating dom validations like a button inside a button, but it’ll still let us do it. And so does React, letting us pass any JSX element as a child to any component that can take children. So, yeah, we learned that we can’t do this at the type level. So, can we do it elsewhere?

The runtime solution

Typescript can’t do it. But this is JS, where everything is possible and the points don’t matter. So, we can loop through the children and check their type. Then, blow everything up if it doesn’t match what we wanted. Something like this:

const ParentComponent: React.FCParentComponentProps> = ( children >) =>  children.forEach((child) =>  if (child.type !== ChildComponent)  throw new Error("Only ChildComponents allowed!"); > >); return <>children>/>; >; 

While this works. it’s not ideal. We don’t want our typed component to break at runtime because the person using it didn’t know that it would break rules set in place by the framework itself. Let’s not do that 😅.

The one that doesn’t actually use children

There’s another option to keep things typesafe and kind of get the end result we want. only it skips the usage of the children prop entirely. You probably already have an idea where I’m going with this:

interface ParentComponentProps  childrenProps: ArrayChildComponentProps>; > const ParentComponent: React.FCParentComponentProps> = ( childrenProps >) =>  return ( <> childrenProps.map((props) => ( ChildComponent  props> /> ))> /> ); >; 

This way, our component will only render ChildComponents and it will be typesafe at usage. But it bypasses the whole idea about using children 🙈.

Other options?

There’s a few other things that work. Instead of throwing an error, we could ignore that element and only render the ones that fulfill the type constraint. Or, we could assert on the existence of a prop in the child instead of the type, to keep it a bit less strict while also making sure that the children contain the data we need to render them correctly. There’s a lot we can do. doesn’t mean we should do it.

Final words

I still believe that children are best reserved for libraries that concern themselves with wrapping components in order to enhance them. Think, CSS in JS, or stuff involving the Context api that wants to wrap things in Providers . Does it look cool to do stuff like this?

const Usage = () => ( ParentComponent> ChildComponent a=1> b="First Child" /> ChildComponent a=2> b="Second Child" /> /ParentComponent> ); 

Sure it does. And it has its pros, like every child having their own children and making the ParentComponent ‘s api very flexible. But the cost for this, is runtime behaviour that will need to be explained in out of code documentation and maintained fresh in the minds of any developer using this component. Given that writing good docs is one of the hardest tasks in software, I’d say that cost is too high for most cases.

Shoutouts to Felipe who consistenly comes up with interesting ideas like this one. They usually end up in both of us learning a lot (and still disagreeing about the children property).

Источник

React Children with TypeScript

The React children prop allows components to be composed together and is a key concept for building reusable components. Visually, we can think of it as a hole in the component where the consumer controls what is rendered. This post covers different approaches to strongly-typing this powerful and flexible prop with TypeScript.

React Children with TypeScript

Using the FC type

There is a standard React type, FC , that we can use on arrow function components. FC stands for Function Component, and it aliases a type called FunctionComponent .

type Props =  title: string, >; const Page: React.FCProps> = (< title, children, >) => ( div> h1>title>h1> children> div> );

FC is a generic type that takes in the specific type for all the component props. In the example above, we passed in a Props type alias containing a title prop. Alternatively, an interface could be used to define Props .

Notice that children isn’t defined in Props . Instead, it is already defined in the FC type. This is nice if you know this fact, but it might be a bit confusing if you don’t.

Explicitly defining the children prop type

If we explicitly define the children prop type, we have several different options for its type.

Let’s go through them one by one …

Using JSX.Element

Let’s try JSX.Element for starters:

type Props =  title: string, children: JSX.Element,>; const Page = ( title, children >: Props) => ( div> h1>title>h1> children> div> );

children is required at the moment. If we want to make this optional for the consumer of the component, we put a question mark( ? ) before the type annotation:

type Props =  title: string; children?: JSX.Element;>;

JSX.Element is good if the child is required to be a single React element. However, it doesn’t allow multiple children. So, we could make the following adjustment:

type Props =  title: string; children?: JSX.Element | JSX.Element[];>;

Using ReactChild

A downside of JSX.Element is that it doesn’t allow strings. So, we could add strings to the union type:

type Props =  title: string; children: | JSX.Element | JSX.Element[] | string | string[];>;

… this is getting out of hand!

Fortunately, there is a standard type called ReactChild that includes React elements, strings and numbers. So, we could widen the type for children as follows:

type Props =  title: string; children?: | React.ReactChild | React.ReactChild[];>;

Using ReactNode

React.ReactChild | React.ReactChild[] gives the breadth of values we need, but is a little verbose. ReactNode is a more terse option:

type Props =  title: string; children?: React.ReactNode;>;

ReactNode allows multiple elements, strings, numbers, fragments, portals, …

The FC generic type uses ReactNode under the hood as well.

Class components

What if we are using a class component? Let’s explore this:

type Props =  title: string, >; export class Page extends React.ComponentProps>  render()  return ( div> h1>this.props.title>h1> this.props.children> div> ); > >

Like FC , the Component type automatically includes the children prop.

If we hover over the children prop, we discover the type it has been given:

Class children

So, the type of the children prop in a class component is ReactNode as well.

Wrap up

If we are using function components with the FC type, the children prop is already typed. If we explicitly type the children prop, the ReactNode is generally the best choice.

Did you find this post useful?

If you to learn more about using TypeScript with React, you may find my course useful:

Источник

Читайте также:  Servers java in lineage 2
Оцените статью