# 🔍 How React Works Under the Hood

🛠️ *Purpose:* This guide is a foundational deep-dive into React’s internals, crafted for software artisans preparing to migrate class components to modern function components. We’ll walk through React’s core concepts step by step – from the **Virtual DOM** and JSX to how React **renders**, **reconciles**, and **commits** updates – and highlight the key differences between class and function components. Each section is organized with clear headers, lists, and emoji markers to make the journey easy to follow and reference.

## 🖼️ Virtual DOM: Declarative Rendering with In-Memory Diffing

React maintains an *idealized* representation of the UI in memory, often called the **Virtual DOM (VDOM)**. In this model, your React component tree is represented by lightweight JavaScript objects (React elements) rather than direct DOM nodes. When your state or props change, React computes a new virtual DOM and *reconciles* it with the previous one to determine what real DOM updates are needed. This diffing process lets React batch and minimize expensive DOM operations. In the React docs:

> “The virtual DOM (VDOM) is a programming concept where an ideal, or ‘virtual’, representation of a UI is kept in memory and synced with the ‘real’ DOM… This process is called **reconciliation**.”

By abstracting the DOM updates, React lets you write code declaratively (“tell React *what* the UI should be”) while it handles the imperative work (“React makes sure the DOM matches that state”). Because JavaScript object updates are much faster than raw DOM manipulation, React can compute changes offscreen and apply only the necessary updates to the real DOM. In practice, a JSX expression compiles into a *React element* – a plain object with keys like `type`, `props`, and `children`. On each update, React compares the new element objects with the old ones (using component type and keys) and keeps, moves, or removes DOM nodes as needed.

## 📝 JSX and React Elements: From Syntax to Objects

React’s JSX syntax is just syntactic sugar that ultimately becomes calls to `React.createElement(...)`. For example, JSX like `<h1>Hello</h1>` is compiled (by Babel) into `React.createElement("h1", null, "Hello")`. Each call produces a **React element** – a JS object describing that element. React elements are **immutable descriptors** of the UI. A simplified element object looks like:

```js
{ 
  $$typeof: Symbol(react.element), 
  type: 'h1', 
  props: { children: 'Hello', className: 'title' }, 
  key: null, 
  ref: null 
}
```

React elements may also represent component types (functions or classes) and can contain nested children, keyed lists, etc.

When you update state, React “re-renders” by calling your components (or functions) and producing a new tree of these element objects. React then **diffs** the old vs new trees:

* It assumes elements of *different types* produce completely different subtrees (so it will tear down and rebuild those nodes).
    
* It uses `key` props to match corresponding items in lists.
    

As one write-up describes, “Whenever something changes, React tries to compare the previous object with the current and decides what to keep, discard and re-create.” In other words, JSX → `createElement` gives objects, and React’s reconciliation steps determine the minimal set of DOM updates needed to align the UI with those objects.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1762650367211/27ceb847-6cfb-4579-9961-fedc47cd066c.png align="center")

## ⚙️ React Fiber, Rendering & Reconciliation

React’s **Fiber** architecture is its internal reconciliation engine (since React 16). Think of the Fiber tree as a linked data structure of “work units” corresponding to your component tree. Fiber’s headline feature is *incremental* and *prioritized* rendering. In the render phase, React traverses your tree of components (via your `render()` methods or function bodies) and creates/updates the Fiber nodes and the virtual DOM. This phase builds a “work-in-progress” tree and figures out what changed, **without** touching the real DOM yet.

Key points about Fiber:

* **Render Phase (Reconciliation):** React creates or updates fiber nodes for each element, computes the diff, and prepares a list of updates. This phase is *interruptible*: React can pause, yield control (to keep the UI responsive), and resume work. It uses a priority scheduler to update urgent changes (like user input) first. Importantly, no DOM mutations happen yet.
    
* **Commit Phase:** Once the diff is ready, React enters the commit phase to apply all changes to the real DOM **synchronously**. In this phase React *applies* the updates (inserts, updates, or removes actual DOM nodes) and invokes lifecycle methods or effect callbacks.
    

These phases ensure React does most heavy work (diffing) off-DOM and only syncs with the browser when ready. By design, reconciliation (diffing) and rendering are *separate* concerns: the former figures out *what* should change (per-component, in virtual DOM), and the latter (ReactDOM or React Native renderer) actually updates the target environment. This separation even allows React to target different platforms (browser DOM vs native) with the same core algorithm.

### 📋 Rendering Steps (Trigger → Render → Commit)

In practice, any React update goes through three steps:

1. **Trigger:** React begins an update because the root component was mounted, or state/props changed. (e.g. calling `root.render(<App/>)` or `setState/useState`).
    
2. **Render (Reconciliation):** React calls your components’ render functions (or executes function components) to produce a new element tree. It recursively descends into children, building a fresh virtual DOM and Fiber tree. React then *diffs* this new tree against the previous one to determine changes.
    
3. **Commit:** React applies the changes: it updates the real DOM via minimal operations and runs any queued effects or lifecycles.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1762650427047/3c83c038-9c48-4a76-af04-3ffdd34ceb2d.png align="center")

## ⏱️ Component Lifecycle (Mount, Update, Unmount)

Each component (whether class or function with Hooks) goes through mount → update(s) → unmount phases, during which side-effects may run.

**Class Components**

* `constructor()`
    
* `render()`
    
* `componentDidMount()` (after initial commit)
    
* `shouldComponentUpdate()`
    
* `componentDidUpdate()`
    
* `componentWillUnmount()`
    

**Function Components (via Hooks)**

* `useEffect(() => { ... }, [])` → mount effect
    
* `useEffect(() => { ... }, [dep])` → update effect
    
* `return () => { ... }` inside `useEffect` → cleanup on unmount
    

Function components don’t have an instance or lifecyle methods, but React still manages mount/update/unmount. The **Hooks system** provides a hook into these stages. `useEffect` behaves like `componentDidMount` + `componentDidUpdate`, and its return value behaves like `componentWillUnmount`.

## ⚖️ Class vs Function Components

Key differences in how they work internally and how you write them:

* **Syntax and State:**  
    Class: `this.state = { ... }` + `this.setState()`  
    Function: `const [state, setState] = useState(...)`
    
* **Lifecycle and Effects:**  
    Class: lifecycle methods (`componentDidMount`, etc.)  
    Function: `useEffect`, `useLayoutEffect`, `useRef`, etc.
    
* **Boilerplate and Readability:**  
    Function components are generally more concise and less error-prone. They avoid `this` binding issues, and logic can be split across multiple hooks.
    
* **Concurrent Features Compatibility:**  
    New React features (e.g. Suspense, transitions) are designed with function components in mind.
    

## 🧰 Hook Classification for Migration Clarity

As you prepare to transition from class components to functional ones, understanding Hooks is non-negotiable. They’re the modern gateway to managing state, effects, and performance in React.

Let’s look at Hooks through two lenses: **what they do** and **how complex/important they are** during development and interviews. This classification helps map them to class lifecycle equivalents and identify when each is appropriate.

### 🧭 Classification by Meaning

#### 🗂️ State Management Hooks

* `useState` – Add local component state.
    
* `useContext` – Share values across the tree without prop drilling.
    
* `useReducer` – Handle complex state logic with reducers (Redux-style).
    

#### 🔁 Side Effect Hooks

* `useEffect` – Run side effects after render (e.g., fetches, subscriptions).
    
* `useLayoutEffect` – Run effects before the browser paints.
    
* `useInsertionEffect` – Inject styles or other low-level layout changes before all layout effects.
    
* `useImperativeHandle` – Customize exposed refs with `forwardRef`.
    

#### ⚡ Performance Optimization Hooks

* `useMemo` – Memoize expensive calculations.
    
* `useCallback` – Memoize function instances between renders.
    
* `useDeferredValue` – Defer updates for low-priority state.
    
* `useTransition` – Mark parts of updates as non-urgent.
    

#### 📦 Reference and Mutable Access Hooks

* `useRef` – Persist a mutable value across renders (e.g., DOM refs).
    
* `useId` – Generate unique IDs across renders/server.
    
* `useSyncExternalStore` – Subscribe to external sources (e.g., global state or stores).
    

---

### 🧮 Classification by Complexity, Lifecycle, and Importance

| Hook | Complexity | Lifecycle Phase | Developer Importance |
| --- | --- | --- | --- |
| `useState` | Basic | Update (triggers re-render) | ⭐️ High |
| `useEffect` | Basic | Mount, Update, Unmount | ⭐️ High |
| `useContext` | Basic | Always active (re-renders on change) | ⭐️ High |
| `useReducer` | Intermediate | Similar to state update | ⭐️ Medium |
| `useCallback` | Intermediate | Memoization | ⭐️ Medium |
| `useMemo` | Intermediate | Memoization | ⭐️ Medium |
| `useRef` | Intermediate | Persistent between renders | ⭐️ Medium |
| `useLayoutEffect` | Advanced | Before paint | ⭐️ Low |
| `useImperativeHandle` | Advanced | With forwarded refs | ⭐️ Low |
| `useDebugValue` | Advanced | Debugging custom hooks | ⭐️ Low |
| `useTransition` | Advanced | Marks updates as "transition" | ⭐️ Low–Medium |
| `useDeferredValue` | Advanced | Delayed updates | ⭐️ Low |
| `useId` | Advanced | Initialization | ⭐️ Low–Medium |
| `useSyncExternalStore` | Advanced | Subscription | ⭐️ Low |
| `useInsertionEffect` | Advanced | Before layout effects | ⭐️ Low |

> 💡 *Note:* Don’t worry if this list feels overwhelming. You’ll rarely use all of them. Start with the basics (`useState`, `useEffect`, `useContext`) and grow from there as your app demands more power or precision.

---

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1762650281058/b635899c-6e48-4205-9680-e2f2253648c5.png align="center")

By classifying Hooks this way, you'll have a mental map for deciding what to use, when—and how it compares to the lifecycle methods you're migrating from. In future articles, we’ll walk you through mapping class lifecycles to function hooks with side-by-side code samples and caveats.

## 🚀 Putting It All Together

React’s rendering pipeline:

* **JSX** → transformed into **React elements (objects)**
    
* **Fiber** builds a virtual DOM tree and diffs it against the previous tree
    
* **Commit Phase** applies updates to the real DOM and runs side effects
    

By understanding these internals, you'll be better equipped to migrate class components, create efficient functional components, and visualize how your code interacts with React's rendering lifecycle.🎯 **Key Takeaway:** Class and function components feed into the same reconciliation engine. Migrating from one to the other means shifting syntax and lifecycle logic, not the rendering model. Know the internals, and your migrations will be precise, elegant, and future-proof.
