skip to content

Files containing JSX no longer need `import React from 'react'` at the top. What changed in the compiled output to make that possible, and when do you still need to import React?

level: middleimportance: should knowfreq 54%

answer

  1. the import served generated code
  2. two compile targets, not two React versions
  3. a separate module specifier
  4. jsxImportSource redirects it
  5. still needed when you write React. yourself

basics

~20 s

The automatic JSX runtime, added in React 17, makes the compiler inject its own import of jsx from react/jsx-runtime instead of emitting React.createElement. You still import React when your code names a React API directly, such as React.Component or React.memo.

solid answer

~50 s

The classic transform compiled `<div />` to `React.createElement("div", null)`, so the identifier `React` had to be in scope in every JSX file — the import existed to satisfy generated code, not your code. The **automatic runtime**, shipped in React 17 and standard by React 19, compiles the same tag to `_jsx("div", {})` and injects `import { jsx as _jsx } from "react/jsx-runtime"` at the top of the module itself. Development builds import `jsxDEV` from `react/jsx-dev-runtime` instead, which carries file and line information for warnings. You enable it with Babel's `@babel/preset-react` at `runtime: "automatic"`, TypeScript's `"jsx": "react-jsx"`, or the equivalent SWC setting — Next.js and Vite's React plugin do it for you. You still need an explicit React import whenever the source itself references a React API through the namespace, such as `React.Component`, `React.memo`, or `React.createElement`.

code

json · 10 lines
json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "react",
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true
  }
}

go deeper

for a junior

Know that modern React projects do not need import React from 'react' for JSX, and that hooks and other APIs are still imported by name from react.

for a middle

Explain the mechanism: the compiler now injects import { jsx } from "react/jsx-runtime" itself, and the old import only ever existed to satisfy React.createElement in the generated code.

for a senior

Be able to configure and diagnose it — the Babel, TypeScript, and SWC settings involved, the dev runtime's role in warning locations, and why the react/react-in-jsx-scope lint rule must come off during migration.

for a principal

Own it as a build-pipeline decision across packages: one transform setting per package, the blast radius of pointing jsxImportSource at a styling library, and how mixed classic/automatic configs in a monorepo produce confusing per-package failures.

## What the import was ever for Under the **classic** JSX transform, the compiler emitted calls to `React.createElement`. That identifier had to resolve in the module's scope, so every file with JSX carried `import React from "react"` even when nothing in the source text mentioned React. Bundlers could not tree-shake it away, and it was a constant source of "React is not defined" errors at runtime — the single most confusing failure a beginner hits, because the offending code is not visible in the file. ## What the automatic runtime changed React 17 introduced a second compile target, the **automatic runtime**. Instead of assuming a global-ish `React` binding, the compiler adds its own import to each module it transforms: ```js // source export const Badge = () => <span className="badge">new</span>; // output, automatic runtime (production) import { jsx as _jsx } from "react/jsx-runtime"; export const Badge = () => _jsx("span", { className: "badge", children: "new" }); ``` Three things are worth noticing. The import is module-scoped and generated, so bundlers handle it like any other import. The entry point is a **separate module specifier**, `react/jsx-runtime`, not the `react` root — it exports `jsx`, `jsxs`, and `Fragment`. And children moved into the props object, which is where React kept them anyway. Development builds target a different module, `react/jsx-dev-runtime`, whose `jsxDEV` function takes extra arguments describing the source file and line. That is how React attaches a useful location to messages such as a missing-key warning. ## Turning it on The transform is a build-tool setting, not a React setting: ```json // babel.config.json { "presets": [["@babel/preset-react", { "runtime": "automatic" }]] } ``` ```json // tsconfig.json { "compilerOptions": { "jsx": "react-jsx" } } ``` TypeScript's `"jsx"` option takes `"react-jsx"` (automatic), `"react-jsxdev"` (automatic, dev runtime), `"react"` (classic `React.createElement`), or `"preserve"` (leave JSX for a downstream tool). SWC exposes the same choice through its React transform options. In practice Next.js and Vite's React plugin configure the automatic runtime for you, which is why a fresh app has no React import anywhere. One knob rides along: **`jsxImportSource`**. It replaces the package the runtime is imported from, so `"jsxImportSource": "@emotion/react"` makes the compiler emit `import { jsx } from "@emotion/react/jsx-runtime"`, letting that library intercept every element creation — the mechanism behind Emotion's `css` prop. Any package exporting a `/jsx-runtime` module with `jsx`, `jsxs`, and `Fragment` can be a target. ## When you still import React The rule is simple: the import is needed when **your source text** names the `React` namespace, not when JSX appears. Cases that still require it: - `class Boundary extends React.Component { ... }` (though `import { Component } from "react"` works too) - `React.memo(...)`, `React.lazy(...)`, `React.createElement(...)` written by hand — all avoidable with named imports - Type positions in TypeScript such as `React.ReactNode` or `React.FC`, unless you import the types by name - Any file still compiled with the classic runtime, for example a package in a monorepo with its own older Babel config Hooks and other APIs are normally imported by name — `import { useState } from "react"` — which is why most modern components need no default React import at all. ## Related lint and migration notes The ESLint rule `react/react-in-jsx-scope` from `eslint-plugin-react` enforces the *old* requirement and must be turned off in a project on the automatic runtime, otherwise it reports every JSX file. Migrating an existing codebase is mostly mechanical: switch the build setting, disable that rule, and delete the now-unused default imports; leaving them in is harmless but noisy, and a linter for unused imports will flag them once the rule is off. ## The interview-shaped answer Most candidates know "you do not need the import any more" as a fact. The distinguishing answer explains *why*: the import was there to satisfy generated code, the generated code now imports what it needs from `react/jsx-runtime` itself, and the only remaining reason to import React is that you wrote `React.` in your own source.

  • What does the `jsxImportSource` option let you do?
    It changes which package the generated import comes from: with `"jsxImportSource": "@emotion/react"` the compiler emits `import { jsx } from "@emotion/react/jsx-runtime"`, so that library's factory sees every element and can add behaviour such as Emotion's `css` prop. Any package exporting a `/jsx-runtime` module with `jsx`, `jsxs`, and `Fragment` qualifies.
  • How does the development JSX runtime differ from the production one?
    Development builds import `jsxDEV` from `react/jsx-dev-runtime` instead of `jsx` from `react/jsx-runtime`. `jsxDEV` receives extra arguments carrying the source file name and line number, which React uses to point warnings — a missing key, an invalid prop — at the exact JSX that produced the element.

saying these in an interview costs you the question

  • Thinks React 17 removed React.createElement
  • Says the bundler auto-injects a React global now
  • Claims you never need to import React again under any circumstance
  • Confuses the JSX runtime setting with the installed React version
  • Believes the change was made to reduce bundle size

context