Explained: 'React' refers to a UMD global
Find out why this error occurs and learn how to fix it.
When the TypeScript team started work on supporting React, JSX was the big stumbling block. Its syntax doesn't exist in JavaScript, so they had to build it into the compiler.
They came up with the idea for .tsx
files, the jsx
option in tsconfig.json
, and suddenly, JSX was supported. But there was an interesting unanswered question: what type should this function infer as?
JSX.Element
tsx
// When I hover this, what should I get?const Component = () => {return <div>Hello world</div>;};
The answer was a special type called JSX.Element
. If you hover over a component today, you'll likely see:
tsx
// const Component: () => JSX.Element
JSX
is something called a global namespace. It's like an object in the global scope. A namespace can contain types, and Element
is one of those types. This means that if React's type definitions define JSX.Element
,` it'll be picked up by TypeScript.
Here's how it looks in React's type definitions:
typescript
// Puts it in the global scopedeclare global {// Puts it in the JSX namespacenamespace JSX {// Defines the Element interfaceinterface Elementextends React.ReactElement<any, any> {}}}
We can think of JSX.Element
, however it's defined, as representing the thing that calling a JSX expression returns. It's the type of the thing that gets created when you write JSX.
JSX.Element
used for?Now - why would this knowledge be useful to you? What would you want to use the JSX.Element
type for?
The most obvious choice would be for typing the children
property of a component.
tsx
const Component = ({children,}: {children: JSX.Element;}) => {return <div>{children}</div>;};
The issues start to become apparent when you begin using this type. For example, what happens if you want to render a string?
tsx
// 'Component' components don't accept text as// child elements. Text in JSX has the type// 'string', but the expected type of 'children'// is 'Element'.<Component>hello world</Component>
This is perfectly valid - React can handle various things as children of components, like numbers, strings, and even undefined
.
But TypeScript isn't happy. We've made the type of children
JSX.Element
, which only accepts JSX.
We need a different type definition to use for children
. We need a type that accepts strings, numbers, undefined, and JSX.
React.ReactNode
This is where React.ReactNode
comes in. It's a type that accepts everything that React can render.
It lives in the React namespace:
typescript
declare namespace React {type ReactNode =| ReactElement| string| number| ReactFragment| ReactPortal| boolean| null| undefined;}
We can use it to type our children
prop:
tsx
const Component = ({children,}: {children: React.ReactNode;}) => {return <div>{children}</div>;};
Now we can pass in strings, numbers, undefined, and JSX:
tsx
<Component>hello world</Component><Component>{123}</Component><Component>{undefined}</Component><Component><div>Hello world</div></Component>
React.ReactNode
?The only time not to use React.ReactNode
is when we're typing the return type of a component.
tsx
const Component = (): React.ReactNode => {return <div>Hello world</div>;};
It looks okay when defining it, but when we go to use it, it'll freak out:
tsx
// 'Component' cannot be used as a JSX component.// Its return type 'ReactNode' is not a valid JSX element.<Component />
This is because TypeScript uses the definition of JSX.Element
to check if something can be rendered as JSX. And since React.ReactNode
contains things that aren't JSX, it can't be used as a JSX element.
I know, confusing.
TypeScript 5.1 is bringing some changes that will shake things up a bit. Instead of checking for JSX.Element
, TypeScript will check JSX.ElementType
to see what can be rendered as an element. This means that the React team can change the definition of JSX.ElementType
to be a little wider:
typescript
namespace JSX {type ElementType =// All valid lowercase tags| keyof IntrinsicAttributes// Function components| ((props: any) => Element)// Class components| (new (props: any) => ElementClass);}
It's unclear to me how this will shake out in terms of React.ReactNode
- but it's likely that you'll still use it for annotating the children of your React components.
You should almost never use JSX.Element
in your code. It's a type used internally by TypeScript to represent the return type of JSX expressions.
Instead, use React.ReactNode
to type the children of your components. I'd suggest not annotating the return types of your components to avoid confusion.
Once 5.1 drops, remind me to revisit this article, and we can see how things have changed - if at all.
Share this article with your friends
Find out why this error occurs and learn how to fix it.
Since I first got into advanced TypeScript, I've been in love with a particular pattern. It formed the basis for one of my first-ever TypeScript tips, and it's been extraordinarily useful to me ever since. I call it the IIMT (rhymes with 'limped'): the Immediately Indexed Mapped Type.
There are three rules to keep in mind when deciding where to put types in your application code.
Discover the power of ComponentProps in React and TypeScript. Learn how to extract and utilize props from native HTML elements, existing components, and elements with associated refs. Enhance your development workflow with this essential type helper.
Testing types is a crucial aspect of developing libraries in TypeScript. In this article, we explore three different ways to test your types: using the vitest test runner, rolling your own solution with type helpers, and leveraging the tsd library.
The TypeScript 5.1 beta is out - here's everything you need to know.
There’s a difference between using TypeScript and knowing TypeScript.
The docs give you a good grasp of the pieces like generic functions, conditional types, and type helpers.
But out in the wild, developers are combining these pieces together into patterns.
Four of the most important patterns to know and use are:
Testing code doesn't need to be typed so strictly, and sometimes tests need to pass the wrong type. I made a library called shoehorn that eases the pain of working with tests in TypeScript by providing a first-class way to pass the wrong type to a function.
The article discusses why TypeScript does not throw an error when a function that is assigned to a variable doesn't match its type. It explains that a function with fewer parameters than its type can still be passed, and this behavior is not restricted to TypeScript but exists in JavaScript as well.
TypeScript 5.0 introduces const type parameters which are useful in preserving the literal types of objects passed to functions.
Updates to TypeScript 5.0 have made their way into Total TypeScript!
Exclude
is a very powerful utility type that can be used in a variety of ways. In this article, I'll show you 9 ways to use it along with code examples.
As a frontend developer, your job isn't just pixel-pushing. Most of the complexity in frontend comes from handling all the various states your app can be in.
It might be loading data, waiting for a form to be filled in, or sending a telemetry event - or all three at the same time.
If you aren't handling your states properly, you're likely to come unstuck. And handling states starts with how th
Using the satisfies keyword is one of four ways to make type assignments in TypeScript. In this article we'll look at examples of when each method should be used.
Understand why TypeScript throws complicated errors by learning how to read them. Errors mirror the structure of the code being compared and can be simplified by changing the way types are assigned.
Learn how to use TypeScript generics on the type level and with functions to improve code readability, type safety, and reduce repetitive code. Use "type helpers" to create new types and generic functions to pass in and return specific types.
Use Zod to validate unknown inputs in your app, whether it's a CLI or a public API, to ensure that data entering your app is safe. Zod can also be used for 'sort of' trusted inputs, like third-party services, to ensure that the data returned is what you expect.
TypeScript's template literal syntax allows developers to manipulate and transform strings in a powerful way. This can be extended using unions, infer, and recursion to handle more complex tasks.
Donny (kdy1 on GitHub) is rewriting TypeScript in Rust hoping to speed up tsc which is slow due to its TypeScript base. In this interview, he explains some of the financial and technical challenges they face with this open-source project.
Let's imagine you're creating a function which sums up an array of objects. Here's one, taken from the Excalidraw codebase:
const sum = <T>(array: readonly T[], mapper: (item: T) => number): number => array.reduce((acc, item) => acc + mapper(item), 0)
Let's look at the type definition. This function takes in:
readonly T[]