All Articles

You Can't Make Children "Type Safe" in React & TypeScript

Matt Pocock
Matt PocockMatt is a well-regarded TypeScript expert known for his ability to demystify complex TypeScript concepts.

If you've worked with TypeScript and JSX for enough time, you'll probably have run into a situation where you want to restrict a component's children to be of a certain type.

Let's say you're working on a Select component that takes Option components as children. You want to make sure that the Option components are the only children that can be passed to Select.

// GOOD

<Select>
  <Option value="1">One</Option>
  <Option value="2">Two</Option>
  <Option value="3">Three</Option>
</Select>

// BAD

<Select>
  <div>One</div>
  <div>Two</div>
  <div>Three</div>
</Select>

It might feel like there are options available to you. Perhaps using ReactElement, or ReactNode, or ReactChild. But as it turns out, nothing works.

You can't tell TypeScript 'only use this type of component as children'.

<Component /> is Always JSX.Element

The reason for this comes down to the way that TypeScript interprets JSX.

Whenever TypeScript sees a JSX tag, it treats it as a JSX.Element. It doesn't matter what the component is, or what its props are. It's always a JSX.Element.

const element1 = <Component />;
const element1: JSX.Element
const element2 = <div />;
const element2: JSX.Element
const element3 = <span />;
const element3: JSX.Element

Let's imagine that we wanted to make our Select component only accept Option components as children. We might try to do something like this:

type SelectProps = {
  children: ReactElement<OptionProps>[];
};

But our Option components always return JSX.Element.

Attempting To Override The Return Type

This is even true if we use as to override the return type of the component:

const Option = () =>
  (<option />) as any as "I should be showing below!";

const element = <Option />;
const element: JSX.Element

Funnily enough, this does work if you call Option() manually:

const element = Option();
const element: "I should be showing below!"

Because this bypasses the JSX interpretation, TypeScript is able to understand that Option returns our special string.

But calling Option() manually is a terrible idea in React - it breaks all sorts of assumptions React makes about your code and will immediately cause bugs.

So, this approach just doesn't work.

Conclusion

It's not possible to restrict the children of a React component to a certain type in TypeScript.

But here's something to ponder - would you even really want to?

The magic of React is that components can be composed in any way you like. By restricting the children of a component, you're breaking that composability.

Perhaps instead of restricting the type of children passed to a component, you could use a prop instead:

<Select
  options={[
    { value: "1", label: "One" },
    { value: "2", label: "Two" },
    { value: "3", label: "Three" },
  ]}
/>

This way, you can still restrict the type of the children, but you're not breaking the composability of your components. Learn why it's not possible to restrict the type of children in React component with TypeScript Explore alternative approaches for component composition.

Matt's signature

Share this article with your friends