Skip to content

Update JSX documentation #3064

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 92 additions & 15 deletions packages/documentation/copy/en/reference/JSX.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ In order to use JSX you must do two things.
1. Name your files with a `.tsx` extension
2. Enable the [`jsx`](/tsconfig#jsx) option

TypeScript ships with three JSX modes: `preserve`, `react`, and `react-native`.
These modes only affect the emit stage - type checking is unaffected.
TypeScript ships with several JSX modes: `preserve`, `react` (classic runtime), `react-jsx` (automatic runtime), `react-jsxdev` (automatic development runtime), and `react-native`.
The `preserve` mode will keep the JSX as part of the output to be further consumed by another transform step (e.g. [Babel](https://babeljs.io/)).
Additionally the output will have a `.jsx` file extension.
The `react` mode will emit `React.createElement`, does not need to go through a JSX transformation before use, and the output will have a `.js` file extension.
Expand Down Expand Up @@ -70,14 +69,73 @@ This is important for two reasons:
TypeScript uses the [same convention that React does](http://facebook.github.io/react/docs/jsx-in-depth.html#html-tags-vs.-react-components) for distinguishing between these.
An intrinsic element always begins with a lowercase letter, and a value-based element always begins with an uppercase letter.

### The `JSX` namespace

JSX in TypeScript is typed by the `JSX` namespace. The `JSX` namespace may be defined in various places, depending on the `jsx` compiler option.

The `jsx` options `preserve`, `react`, and `react-native` use the type definitions for classic runtime. This means a variable needs to be in scope that’s determined by the `jsxFactory` compiler option. The `JSX` namespace should be specified on the top-most identifier of the JSX factory. For example, React uses the default factory `React.createElement`. This means its `JSX` namespace should be defined as `React.JSX`.

```ts
export function createElement(): any;

export namespace JSX {
// …
}
```

And the user should always import React as `React`.

```ts
import * as React from 'react';
```

Preact uses the JSX factory `h`. That means its types should be defined as the `h.JSX`.

```ts
export function h(props: any): any;

export namespace h.JSX {
// …
}
```

The user should use a named import to import `h`.

```ts
import { h } from 'preact';
```

For the `jsx` options `react-jsx` and `react-jsxdev`, the `JSX` namespace should be exported from the matching entry points. For `react-jsx` this is `${jsxImportSource}/jsx-runtime`. For `react-jsxdev`, this is `${jsxImportSource}/jsx-dev-runtime`. Since these don’t use a file extension, you must use the [`exports`](https://nodejs.org/api/packages.html#exports) field in `package.json` map in order to support ESM users.

```json
{
"exports": {
"./jsx-runtime": "./jsx-runtime.js",
"./jsx-dev-runtime": "./jsx-dev-runtime.js",
}
}
```

Then in `jsx-runtime.d.ts` and `jsx-dev-runtime.d.ts`:

```ts
export namespace JSX {
// …
}
```

Note that while exporting the `JSX` namespace is sufficient for type checking, the production runtime needs the `jsx`, `jsxs`, and `Fragment` exports at runtime, and the development runtime needs `jsxDEV` and `Fragment`. Ideally you add types for those too.

If the `JSX` namespace isn’t available in the appropriate location, both the classic and the automatic runtime fall back to the global `JSX` namespace.

### Intrinsic elements

Intrinsic elements are looked up on the special interface `JSX.IntrinsicElements`.
By default, if this interface is not specified, then anything goes and intrinsic elements will not be type checked.
However, if this interface _is_ present, then the name of the intrinsic element is looked up as a property on the `JSX.IntrinsicElements` interface.
For example:

```ts
```tsx
declare namespace JSX {
interface IntrinsicElements {
foo: any;
Expand All @@ -104,7 +162,7 @@ declare namespace JSX {

Value-based elements are simply looked up by identifiers that are in scope.

```ts
```tsx
import MyComponent from "./myComponent";

<MyComponent />; // ok
Expand All @@ -123,7 +181,7 @@ Because these two types of value-based elements are indistinguishable from each
As the name suggests, the component is defined as a JavaScript function where its first argument is a `props` object.
TS enforces that its return type must be assignable to `JSX.Element`.

```ts
```tsx
interface FooProp {
name: string;
X: number;
Expand Down Expand Up @@ -211,7 +269,7 @@ const myComponent = MyFactoryFunction();
The element instance type is interesting because it must be assignable to `JSX.ElementClass` or it will result in an error.
By default `JSX.ElementClass` is `{}`, but it can be augmented to limit the use of JSX to only those types that conform to the proper interface.

```ts
```tsx
declare namespace JSX {
interface ElementClass {
render: any;
Expand Down Expand Up @@ -244,7 +302,7 @@ This is slightly different between intrinsic and value-based elements.

For intrinsic elements, it is the type of the property on `JSX.IntrinsicElements`

```ts
```tsx
declare namespace JSX {
interface IntrinsicElements {
foo: { bar?: boolean };
Expand All @@ -262,7 +320,7 @@ It should be declared with a single property.
The name of that property is then used.
As of TypeScript 2.8, if `JSX.ElementAttributesProperty` is not provided, the type of first parameter of the class element's constructor or Function Component's call will be used instead.

```ts
```tsx
declare namespace JSX {
interface ElementAttributesProperty {
props; // specify the property name to use
Expand All @@ -283,7 +341,7 @@ class MyComponent {
The element attribute type is used to type check the attributes in the JSX.
Optional and required properties are supported.

```ts
```tsx
declare namespace JSX {
interface IntrinsicElements {
foo: { requiredProp: string; optionalProp?: number };
Expand All @@ -304,7 +362,7 @@ Additionally, the `JSX.IntrinsicAttributes` interface can be used to specify ext

The spread operator also works:

```ts
```tsx
const props = { requiredProp: "bar" };
<foo {...props} />; // ok

Expand All @@ -326,7 +384,7 @@ declare namespace JSX {
}
```

```ts
```tsx
<div>
<h1>Hello</h1>
</div>;
Expand All @@ -345,7 +403,7 @@ const CustomComp = (props) => <div>{props.children}</div>

You can specify the type of _children_ like any other attribute. This will override the default type from, e.g. the [React typings](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react) if you use them.

```ts
```tsx
interface PropsType {
children: JSX.Element
name: string
Expand Down Expand Up @@ -386,11 +444,30 @@ You can customize the type by specifying the `JSX.Element` interface.
However, it is not possible to retrieve type information about the element, attributes or children of the JSX from this interface.
It is a black box.

## The JSX function return type

By default, function components must return `JSX.Element | null`. However, this doesn’t always represent runtime behaviour. As of TypeScript 5.1, you can specify `JSX.ElementType` to override what is a valid JSX component type. Note that this doesn’t define what props are valid. The type of props is always defined by the first argument of the component that’s passed. The default looks something like this:

```ts
namespace JSX {
export type ElementType =
// All the valid lowercase tags
keyof IntrinsicAttributes
// Function components
(props: any) => Element
// Class components
new (props: any) => ElementClass;
export interface IntrinsicAttributes extends /*...*/ {}
export type Element = /*...*/;
export type ElementClass = /*...*/;
}
```

## Embedding Expressions

JSX allows you to embed expressions between tags by surrounding the expressions with curly braces (`{ }`).

```ts
```tsx
const a = (
<div>
{["foo", "bar"].map((i) => (
Expand All @@ -403,7 +480,7 @@ const a = (
The above code will result in an error since you cannot divide a string by a number.
The output, when using the `preserve` option, looks like:

```ts
```tsx
const a = (
<div>
{["foo", "bar"].map(function (i) {
Expand All @@ -418,7 +495,7 @@ const a = (
To use JSX with React you should use the [React typings](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react).
These typings define the `JSX` namespace appropriately for use with React.

```ts
```tsx
/// <reference path="react.d.ts" />

interface Props {
Expand Down
16 changes: 16 additions & 0 deletions packages/tsconfig-reference/copy/en/options/jsx.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,19 @@ declare module JSX {
// @jsx: react-jsxdev
export const HelloWorld = () => <h1>Hello world</h1>;
```

This option can be used on a per-file basis too using an `@jsxRuntime` comment.

Always use the classic runtime (`"react"`) for this file:

```tsx
/* @jsxRuntime classic */
export const HelloWorld = () => <h1>Hello world</h1>;
```

Always use the automatic runtime (`"react-jsx"`) for this file:

```tsx
/* @jsxRuntime automatic */
export const HelloWorld = () => <h1>Hello world</h1>;
```