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.

tsx
// 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.

tsx
const element1 = <Component />;
const element1: React.JSX.Element
 
const element2 = <div />;
const element2: React.JSX.Element
 
const element3 = <span />;
const element3: React.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:

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

tsx
const Option = () =>
(<option />) as any as "I should be showing below!";
 
const element = <Option />;
const element: React.JSX.Element

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

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

tsx
<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

`any` Considered Harmful, Except For These Cases

Discover when it's appropriate to use TypeScript's any type despite its risks. Learn about legitimate cases where any is necessary.

Matt Pocock
Matt Pocock

No, TypeScript Types Don't Exist At Runtime

Learn why TypeScript's types don't exist at runtime. Discover how TypeScript compiles down to JavaScript and how it differs from other strongly-typed languages.

Matt Pocock
Matt Pocock

Deriving vs Decoupling: When NOT To Be A TypeScript Wizard

In this book teaser, we discuss deriving vs decoupling your types: when building relationships between your types or segregating them makes sense.

Matt Pocock
Matt Pocock

NoInfer: TypeScript 5.4's New Utility Type

Learn how TypeScript's new utility type, NoInfer, can improve inference behavior by controlling where types are inferred in generic functions.

Matt Pocock
Matt Pocock