facebook/react
Patterns and conventions
The repo's style is enforced by Prettier (prettier-3 for almost everything, prettier-2 for some compiler legacy code), ESLint (.eslintrc.js plus the custom rules in scripts/eslint-rules/), and Flow (for the runtime). Beyond that, there are several conventions that aren't auto-enforced but show up in nearly every file.
File layout
- One module per file. Top-of-file copyright header (BSD-MIT) is auto-checked.
- Type imports use
import type { X } from '...'(Flow / TypeScript). - Internal cross-package imports go through
packages/shared/. A custom rulescripts/eslint-rules/no-cross-fork-imports.jsenforces that no package directly imports from another renderer or from a fork-specific file. - Everything in
packages/<pkg>/src/forks/is fork-resolved at build time; do not import from another package'sforks/directly.
Module entry points
A package's package.json typically declares many entry points via the exports field, with a react-server condition. The shape is:
{
"name": "react",
"exports": {
".": {
"react-server": "./react.react-server.js",
"default": "./index.js"
},
"./jsx-runtime": {
"react-server": "./jsx-runtime.react-server.js",
"default": "./jsx-runtime.js"
}
}
}The react-server condition is what lets react and react-dom expose a different surface when they're running on the server side of an RSC pipeline (no client hooks, etc.). Your bundler (or the npm exports resolver) picks the right file.
Errors and invariants
Runtime errors are written as plain throw new Error('...'). The build-time tool scripts/error-codes/extract-errors.js finds these literal strings, registers them in scripts/error-codes/codes.json, and rewrites the production bundle to throw an integer code instead:
// In source:
throw new Error(
'Hooks can only be called inside the body of a function component.'
);
// In production bundle:
throw Error(formatProdErrorMessage(321));Conventions:
- Always include enough context that the message is understandable on its own; a user on the npm bundle will see the message via
react.dev/errors/321. - Prefer
throw new Error(...)over invariants. (The oldinvariant(cond, msg)helper has been removed in favor of plainif (!cond) throw new Error(msg);.) - For developer-only diagnostics, use
console.error(...)inside anif (__DEV__)block. Theprint-warningsscript (scripts/print-warnings/) collects them all.
__DEV__, __PROFILE__, __EXPERIMENTAL__
These are globals that the Rollup replace plugin substitutes per-build:
| Global | Meaning |
|---|---|
__DEV__ |
True in development, false in production. Gates all warnings and assertion-only code. |
__PROFILE__ |
True in profiling builds. Gates code that emits Performance marks for the Profiler tab. |
__EXPERIMENTAL__ |
True in the experimental channel. Gates large unfinished features when adding a flag is too granular. |
__VARIANT__ |
True for variant builds; rarely used outside very specific A/B feature flags. |
Always wrap dev-only code in if (__DEV__) { ... } so that the production bundle dead-code-eliminates it. The same goes for __PROFILE__.
Feature flags
Every new feature lands behind a flag in packages/shared/ReactFeatureFlags.js. The pattern:
// Per-channel forks override the default:
// packages/shared/forks/ReactFeatureFlags.www.js
// packages/shared/forks/ReactFeatureFlags.www-modern.js
// packages/shared/forks/ReactFeatureFlags.test-renderer.js
// packages/shared/forks/ReactFeatureFlags.native-fb.js
// packages/shared/forks/ReactFeatureFlags.native-oss.js
// In source:
import { enableNewFeature } from 'shared/ReactFeatureFlags';
// In packages/shared/ReactFeatureFlags.js
export const enableNewFeature = false;
if (enableNewFeature) {
/* ... */
}Tests gate on the flag with the @gate pragma. See testing.
The yarn flags script in the repo root runs scripts/flags/flags.js, which prints a table of every flag and its value per channel — useful when you want to know what a fresh react@experimental actually enables.
Style
Prettier's defaults plus:
- 80-column line length where possible (Prettier's default).
- Single quotes for strings.
- Trailing commas everywhere.
- Arrow functions for callbacks; named
functiondeclarations for top-level functions.
Compiler code uses TypeScript and follows the same Prettier config. Compiler tests use the snap golden-file format described in compiler/CLAUDE.md.
Naming
- File names:
ReactFiberX.js,ReactDOMY.js— the package prefix matches the package the file belongs to. - Internal-only files use the
Internalsuffix or live under aforks/directory. - Test files:
<Name>-test.js(or-test.internal.jsfor tests that mock private flags).
Avoiding dependencies
The runtime has very few production dependencies — by design. New dependencies are heavily scrutinized. The dev-dependency list (in the root package.json) is allowed to grow, but anything that ends up in a published bundle has to clear a high bar.
Cross-package imports — the shared package
packages/shared/ is the dumping ground for tiny utilities used in 2+ packages without forming a deep hierarchy:
packages/shared/ReactSymbols.js—REACT_*_TYPEsymbols.packages/shared/ReactFeatureFlags.js— the central flag table.packages/shared/ReactSharedInternals.js— the inter-package "back door" used by hooks/dispatcher.packages/shared/ReactErrorUtils.js,ReactComponentStackFrame.js,forwardRefIcon.js, etc. — small utilities.
If a utility ends up shared across only the renderer and the reconciler, it usually goes in react-reconciler and is re-exported. If it's used across renderers and react, it belongs in shared.
"Don't break the world" reflexes
- Adding a new fiber tag → update
packages/react-reconciler/src/ReactWorkTags.jsand the matching switch ingetComponentNameFromFiber.js, pluspackages/react-devtools-shared/src/backend/fiber/renderer.js(DevTools needs to know about it too). - Adding a new flag bit → update
packages/react-reconciler/src/ReactFiberFlags.jsand audit every commit-phase walk that sets/checks flags. - Adding a new built-in hook → wire it up in all dispatcher tables (mount, update, rerender, server) in
packages/react-reconciler/src/ReactFiberHooks.js, plus the SSR-side dispatcher inpackages/react-server/src/ReactFizzHooks.js, plus the dispatcher inpackages/react-dom/src/server/. - Adding a new symbol type → register it in
packages/shared/ReactSymbols.jsandpackages/shared/getComponentNameFromType.js.
These are the points where a "small" change can otherwise become a six-PR rollback.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.