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?
answer
- the import served generated code
- two compile targets, not two React versions
- a separate module specifier
- jsxImportSource redirects it
- still needed when you write React. yourself
basics
~20 sThe 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 sThe 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{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react",
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true
}
}go deeper
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.
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.
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.
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