Interactive architecture map of React — Facebook's open-source UI library. Covering the Virtual DOM, Fiber reconciler with priority lanes, hooks system, concurrent features (Suspense, transitions), React Server Components, the synthetic event system, the React Compiler (React Forget), and the build tooling ecosystem.
React is a declarative, component-based JavaScript library for building user interfaces. Created at Facebook in 2013, it pioneered the virtual DOM diffing approach and has since evolved into a sophisticated rendering system with concurrent capabilities, server-side rendering, and an automatic optimizing compiler.
React's core abstraction is the virtual DOM — a lightweight JavaScript representation of the actual DOM. When state changes, React builds a new virtual DOM tree, diffs it against the previous one, and applies only the minimal set of mutations needed to bring the real DOM up to date.
Plain JavaScript objects with a type, key, ref, and props. JSX compiles to React.createElement() calls (or the new JSX transform's jsx()). Elements are immutable descriptions of what the UI should look like at a point in time.
Since React 17, the new JSX transform automatically imports jsx from react/jsx-runtime, eliminating the need for import React. Babel or SWC compile JSX into function calls that create element descriptor objects.
Components are functions (or classes) that accept props and return React elements. Function components are the primary model since React 16.8 (hooks). They compose into a tree that mirrors the UI hierarchy and can be lazy-loaded with React.lazy().
Keys help React identify which items in a list have changed, been added, or removed. Without stable keys, React falls back to index-based diffing which can cause subtle state bugs. Keys guide the reconciler's matching heuristic during tree diffing.
Direct DOM manipulation is expensive — layout, paint, and composite phases are slow. By batching changes into a virtual representation and computing the minimal diff, React avoids unnecessary reflows. The virtual DOM also enables cross-platform rendering: the same component tree can target DOM, native mobile (React Native), canvas, or even terminal UIs.
Fiber is React's reconciliation engine, rewritten from scratch for React 16 (2017). Unlike the old stack reconciler which processed the entire tree synchronously, Fiber breaks rendering work into incremental units that can be paused, aborted, and resumed — enabling concurrent rendering and priority-based scheduling.
Each Fiber node is a JavaScript object representing a unit of work. It holds the component type, pending props, state, effect list, and pointers to form a linked tree: child, sibling, and return (parent). This linked structure allows traversal without recursion, enabling interruptible rendering.
| Field | Purpose | Details |
|---|---|---|
tag |
Fiber type identifier | FunctionComponent, ClassComponent, HostComponent, SuspenseComponent, etc. |
stateNode |
Underlying instance | DOM node for host components, class instance for class components, null for functions |
memoizedState |
Hook linked list | Head of the linked list of hook state objects for function components |
lanes |
Priority bitmask | 32-bit bitmask indicating which priority lanes this fiber has pending work in |
flags |
Side effects | Placement, Update, Deletion, Snapshot, Passive (useEffect), Layout (useLayoutEffect) |
alternate |
Double-buffering | Points to the other version of this fiber (current vs. work-in-progress) |
The core scheduling loop calls performUnitOfWork() in a while-loop, processing one fiber at a time. Each iteration calls beginWork() (render phase, going down) and completeWork() (going up). The loop checks shouldYield() to pause for higher-priority work or browser frames.
React 18 uses a 32-bit lane model for scheduling. Each lane represents a priority level: SyncLane (user input), DefaultLane (normal updates), TransitionLane (startTransition), IdleLane (background). Multiple updates can be batched into the same lane.
React maintains two fiber trees: current (what is on screen) and workInProgress (being built). Each fiber has an alternate pointer to the other version. After committing, the work-in-progress tree becomes the current tree. This avoids visual tearing.
After the render phase completes the entire work-in-progress tree, the commit phase runs synchronously in three sub-phases: beforeMutation (snapshot), mutation (DOM writes), and layout (useLayoutEffect). This phase cannot be interrupted.
The old stack reconciler (React 15 and below) processed the component tree with recursive function calls, making it impossible to pause mid-render. Fiber replaced this with an iterative linked-list traversal where each "unit of work" is a single fiber node. This unlocked concurrent rendering, Suspense, and time-sliced updates.
Introduced in React 16.8, hooks let function components manage state, side effects, context, refs, and more without classes. Under the hood, hooks are stored as a linked list on each fiber's memoizedState field, with a dispatcher that swaps between mount and update implementations.
React's internal ReactCurrentDispatcher swaps the hook implementation based on lifecycle phase. During mount, hooks use mountState, mountEffect, etc. During updates, they use updateState, updateEffect. This is why hooks must be called in the same order every render.
Both use the same underlying queue mechanism. State updates are enqueued as objects with an action (value or reducer function) and a lane priority. The queue is processed during the render phase to compute the new state, with bailout optimization if the result is identical.
useEffect schedules passive effects that run asynchronously after paint. useLayoutEffect runs synchronously during the commit phase's layout sub-phase, before the browser paints. Both compare dependency arrays to skip unnecessary re-runs. Cleanup functions run before the next effect.
Memoization hooks that cache computed values (useMemo) or function references (useCallback) between renders. They compare dependency arrays and return the cached value if deps are unchanged. The React Compiler aims to make these unnecessary by auto-memoizing.
Creates a mutable ref object ({ current: initialValue }) that persists across renders without triggering re-renders when mutated. Used for DOM access, storing previous values, and holding mutable instance-like data in function components.
Subscribes a component to a React context. When the context value changes, all consuming components re-render. The context value is read from the nearest Provider up the fiber tree. Unlike other hooks, useContext doesn't have a dependency array — it always triggers on context change.
Hooks must be called at the top level of a function component (not inside conditions, loops, or nested functions) and only from React function components or custom hooks. This is because React identifies each hook by its call order — the linked list must be traversed in the same sequence every render. The eslint-plugin-react-hooks enforces these rules statically.
React 18 introduced concurrent rendering — the ability to prepare multiple versions of the UI simultaneously. Unlike synchronous rendering, concurrent mode lets React interrupt, prioritize, and discard renders to keep the app responsive during expensive updates.
Marks a state update as non-urgent (a "transition"). React will render the transition in the background and keep the current UI responsive. If a higher-priority update arrives (e.g., user typing), React can interrupt and discard the in-progress transition render. useTransition also provides an isPending flag.
Defers updating a value until after more urgent renders complete. Conceptually similar to debouncing, but integrated into React's scheduler. The old value stays on screen while the new one renders in the background. Useful for expensive derived computations like search filtering.
A component that catches thrown promises from its children and shows a fallback UI until they resolve. Originally for code splitting (React.lazy), now supports data fetching (via framework integrations), and streaming SSR. Multiple Suspense boundaries can nest to create progressive loading states.
React 18 automatically batches all state updates into a single re-render, even inside promises, timeouts, and event handlers. Previously, only React event handlers were batched. This reduces unnecessary renders and improves performance without any code changes.
Shows an optimistic state value during async operations. Takes the current state and an update function; immediately applies the optimistic update while the actual mutation is in flight. Reverts automatically if the mutation fails. Designed for form actions and data mutations.
A new hook that can read the value of a Promise or Context resource. Unlike other hooks, use() can be called conditionally. When passed a Promise, it integrates with Suspense boundaries. This unifies the data-fetching pattern within React's rendering model.
Concurrent React doesn't use threads — it uses cooperative scheduling on the main thread. The work loop yields back to the browser between fiber units using shouldYield() (backed by MessageChannel or requestIdleCallback). This means React rendering never blocks user input for more than ~5ms at a time, even for complex component trees.
React Server Components (RSC) enable components that run exclusively on the server, sending only their rendered output to the client as a serialized payload. This eliminates the need to ship their JavaScript to the browser, reducing bundle size while allowing direct server-side data access.
Components that render only on the server. They can be async, directly access databases, file systems, and APIs without exposing credentials. Their code is never shipped to the client bundle. They cannot use hooks, event handlers, or browser APIs. Marked by default (no directive needed).
The "use client" directive at the top of a file marks the server/client boundary. Components in that file (and its imports) become Client Components — they run on both server (SSR) and client. They can use hooks, event handlers, and browser APIs. This is the interactivity entry point.
Server Components serialize their output into a streaming protocol called "Flight" — a line-delimited format of React element references, serialized props, and client component module references. The client-side React runtime reconstructs the component tree from this payload without needing the server component code.
React 18's renderToPipeableStream() sends HTML progressively as Server Components resolve. Suspense boundaries define streaming chunks — content above a boundary streams immediately, while pending sections show fallbacks. When they resolve, React injects the HTML inline with a script to swap it in.
Functions marked with "use server" that run exclusively on the server but can be called from Client Components as form actions or event handlers. React serializes the arguments, sends them as a POST request, and handles the response — enabling zero-API-route data mutations.
React 18 can hydrate the page in priority order rather than all-at-once. Suspense boundaries define hydration chunks. If a user clicks on a not-yet-hydrated section, React prioritizes hydrating that section first. This dramatically improves Time to Interactive on large pages.
The boundary between Server and Client Components is a serialization boundary. Props passed from Server to Client Components must be serializable (JSON-compatible: strings, numbers, booleans, arrays, plain objects, Date, Map, Set, FormData, and certain React types like elements and lazy components). Functions, class instances, and symbols cannot cross this boundary (except Server Actions via "use server").
React implements its own event system on top of native browser events. Synthetic events provide a cross-browser consistent interface, while event delegation at the root container keeps listener count low and integrates cleanly with React's rendering lifecycle.
React wraps native browser events in SyntheticEvent objects that normalize cross-browser differences (event properties, bubbling behavior, etc.). Synthetic events are pooled for performance — though pooling was removed in React 17. They expose the same interface as native events.
Since React 17, all events are delegated to the root container element (not document as before). A single listener per event type captures all events and dispatches them through React's fiber tree. This enables multiple React roots on the same page and better micro-frontend compatibility.
React assigns lane priorities based on event type. Discrete events (click, keydown) get SyncLane (highest priority). Continuous events (scroll, mousemove) get lower priority. This ensures user input is always processed immediately while scroll handlers can be deferred.
Internally, React's event system uses a plugin architecture: SimpleEventPlugin, EnterLeaveEventPlugin, ChangeEventPlugin, SelectEventPlugin, and BeforeInputEventPlugin. Each plugin knows how to extract its events from native browser events and map them to React props.
React 17 moved event delegation from document to the root container, aligning event timing with the native capture/bubble phases. This fixed long-standing issues with event ordering when multiple React trees or third-party libraries are on the same page. It also dropped event pooling, since modern browsers handle object allocation efficiently.
The React Compiler (originally codenamed "React Forget") is an automatic optimizing compiler that analyzes your React code at build time and inserts memoization automatically. It understands React's rules (pure rendering, hooks rules) and generates equivalent code with optimal useMemo, useCallback, and React.memo applied.
The compiler analyzes data flow in components and automatically wraps values, callbacks, and JSX in memoization guards. It can often do better than manual memoization because it operates on the full dependency graph of your component at the expression level, not just at the hook boundary.
The compiler transforms JavaScript AST into an intermediate representation that models React-specific semantics: component boundaries, hook dependencies, effect scopes, and render-vs-effect code. This IR enables optimizations that a generic JavaScript compiler could not infer.
Ships as a Babel plugin (babel-plugin-react-compiler) that runs during your build. It also has experimental support as an SWC plugin for faster build times. The compiler only transforms function components and hooks — it leaves other JavaScript untouched.
The compiler relies on React's rules: components must be pure functions of their props/state, hooks must follow the rules of hooks, and side effects must be in effects. Code that violates these rules may not be optimizable. The compiler can detect and skip non-conforming code with validation diagnostics.
The React Compiler's goal is to make useMemo, useCallback, and React.memo unnecessary for most applications. The compiler can memoize at a finer granularity than developers typically do manually, often achieving better performance. Instagram has been running the compiler in production since 2024, reporting significant rendering performance improvements.
React's minimal core (just the component model and reconciler) is surrounded by a rich ecosystem of frameworks, build tools, and routing solutions. The React team now recommends using a framework for new projects rather than bare React with a custom setup.
The most popular React framework. Provides file-based routing, static generation (SSG), server-side rendering (SSR), incremental static regeneration (ISR), API routes, middleware, and is the primary adopter of React Server Components. Used by Vercel, deployed globally on their edge network.
React Router is the most-used client-side router. Remix (now merged with React Router v7) adds loader/action data patterns, nested routing with parallel data fetching, progressive enhancement, and server rendering. It runs on any JS runtime (Node, Deno, Cloudflare Workers).
The modern build tool for React development. Uses native ES modules for instant dev server startup and Rollup for production builds. React Fast Refresh is provided via @vitejs/plugin-react (Babel) or @vitejs/plugin-react-swc (SWC, 20x faster transforms). Replaced Create React App as the community standard.
The cross-platform mobile framework using React. Renders to native iOS/Android views instead of DOM. Uses the same Fiber reconciler with a different host config. The New Architecture (Fabric renderer + TurboModules + JSI) provides synchronous native calls and concurrent rendering support.
The managed platform for React Native. Provides a development client, over-the-air updates (EAS Update), build service (EAS Build), file-based routing (Expo Router), universal React for web and mobile, and a curated set of native module bindings with consistent APIs.
Browser extension and standalone tool for inspecting the React component tree, profiling renders, viewing hooks state, examining Suspense boundaries, and debugging re-renders. The Profiler measures component render times and identifies unnecessary re-renders with flame graphs.
React provides built-in state primitives (useState, useReducer, useContext), but complex applications often need additional patterns. The ecosystem offers solutions ranging from centralized stores to atomic state to server-cache synchronization — each addressing different trade-offs.
| Library | Pattern | Key Concept |
|---|---|---|
Redux / RTK |
Centralized store | Single immutable state tree, dispatched actions, pure reducer functions. Redux Toolkit adds opinionated defaults with createSlice and RTK Query for data fetching. |
Zustand |
Minimalist store | Hook-based store with no providers or boilerplate. Uses external subscription with selector-based re-render optimization. ~1KB bundle. |
Jotai |
Atomic state | Bottom-up approach with primitive atoms. Derived atoms compose from other atoms. No selectors needed — components subscribe to exactly the atoms they read. |
TanStack Query |
Server cache | Manages server state (fetching, caching, synchronizing, background updates). Deduplicates requests, handles stale-while-revalidate, retry, pagination, and infinite scroll. |
Recoil |
Graph-based | Facebook's experimental atomic state with atoms and selectors forming a data-flow graph. Supports async selectors and atomFamily for parameterized state. |
Valtio |
Proxy-based | Mutate state directly with JavaScript Proxy tracking. React re-renders only components that access changed properties. Feels like MobX but with React hooks. |
With React Server Components and Server Actions in React 19, much of what was traditionally "client state management" can move to the server. Server Components fetch data directly, Server Actions handle mutations, and useOptimistic manages optimistic UI updates. For many applications, this eliminates the need for a separate client-side data fetching library entirely.