Understanding React Children types
Tags: react, typescript
Published: 2021-09-01 15:36:22

---
title: "Understanding React Children types"
published: true
description: "Two men journey through a framework's api in search of child freedom."
tags: ["React", "Typescript"]
cover_image: https://www.dabolivar.com/images/meta/social-share.png
canonical_url: https://www.dabolivar.com/writing/children-of-the-reacts/
---

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:

```jsx
<Wrapper>
  <HeaderWrapper>
    <p>Something</p>
  </HeaderWrapper>
</Wrapper>
```

That renders into:

```html
<div class="container">
  <div class="header-container">
    <p>Something</p>
  </div>
</div>
```

> 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](https://twitter.com/PipeSalazar) 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:

> How can I constraint the parent component's children type, to be of a single component type?

And thus, we set out on a mighty journey towards type enlightenment.

## 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:

```typescript
interface ChildComponentProps {
  a: number;
  b: string;
}

interface ParentComponentProps {
  children: React.ReactElement<ChildComponentProps>[];
}

const ChildComponent: React.FC<ChildComponentProps> = ({ a, b }) => (
  <p>
    {a} {b}
  </p>
);

const ParentComponent: React.FC<ParentComponentProps> = ({ 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:

```typescript
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:

```typescript
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>
);
```

_Narrator: It did work_

## 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:

```typescript
interface FunctionComponent<P = {}> {
  (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
  propTypes?: WeakValidationMap<P>;
  contextTypes?: ValidationMap<any>;
  defaultProps?: Partial<P>;
  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<P>` is and find this:

```typescript
type PropsWithChildren<P> = 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](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces), where both sides of the intersection have differing definitions of a property with the same name. Remember, our `P` in this case was:

```typescript
interface ParentComponentProps {
  children: React.ReactElement<ChildComponentProps>[];
}
```

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](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types) out of them. What comes out after all this is done is:

```typescript
interface FinalParentComponentProps {
  children: React.Reactelement<ChildComponentProps>[] | 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 `<div>Not correct component</div>` 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 `<div>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:

```typescript
const ParentComponent: React.FC<ParentComponentProps> = ({ 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:

```typescript
interface ParentComponentProps {
  childrenProps: Array<ChildComponentProps>;
}

const ParentComponent: React.FC<ParentComponentProps> = ({ 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?

```typescript
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](https://twitter.com/PipeSalazar) 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).