Skip to main content

Documentation Index

Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt

Use this file to discover all available pages before exploring further.

React Virtual DOM

Introduction to React

React is a JavaScript library for building user interfaces, developed by Facebook (now Meta). It allows developers to build reusable UI components and manage the state of their applications efficiently. At its core, React answers one question: given this data, what should the screen look like? You describe the UI you want (declaratively), and React figures out how to make the browser match that description. This is a fundamentally different approach from imperative DOM manipulation where you write step-by-step instructions (“find this element, change its text, add a class…”).

Why React?

  1. Component-Based: Build encapsulated components that manage their own state, then compose them to make complex UIs. Think of components as LEGO blocks — each one is self-contained, and you snap them together to build anything from a simple button to an entire dashboard.
  2. Declarative: Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes. You say what you want, not how to get there.
  3. Virtual DOM: React uses a virtual DOM to improve performance. It calculates the minimal set of changes needed to update the actual DOM. Think of it like editing a blueprint before sending the construction crew — you plan all the changes first, then apply them in one efficient batch.
  4. Ecosystem: A massive ecosystem of libraries, tools, and community support.

How React Works Under the Hood

Understanding what happens behind the scenes helps you write better React code and debug issues effectively.

JSX Transformation

JSX is not valid JavaScript—it needs to be transformed before the browser can execute it. Tools like Babel or SWC (used by Vite) compile JSX into regular JavaScript function calls.
// What you write (JSX)
const element = <h1 className="greeting">Hello, world!</h1>;

// What it compiles to (JavaScript)
const element = React.createElement(
  'h1',                          // tag type
  { className: 'greeting' },     // props object
  'Hello, world!'                // children
);
For nested elements, it creates nested createElement calls:
// JSX
const app = (
  <div>
    <h1>Title</h1>
    <p>Description</p>
  </div>
);

// Compiled JavaScript
const app = React.createElement(
  'div',
  null,
  React.createElement('h1', null, 'Title'),
  React.createElement('p', null, 'Description')
);
Modern React (17+): Uses jsx-runtime instead of React.createElement, which is slightly more efficient and doesn’t require importing React in every file.

The Virtual DOM Explained

The Virtual DOM is a lightweight JavaScript representation of the actual DOM. React uses it to minimize expensive DOM operations. Real-world analogy: Imagine you are an architect renovating a building. Instead of tearing down walls and rebuilding them every time the client changes their mind, you first update the blueprint (Virtual DOM). You then compare the old blueprint with the new one, identify exactly which walls need to move, and send only those changes to the construction crew (the real DOM). That is reconciliation in a nutshell.
┌─────────────────────────────────────────────────────────────────┐
│                    React Rendering Pipeline                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. State/Props Change                                          │
│         │                                                       │
│         ▼                                                       │
│  2. Create New Virtual DOM Tree                                 │
│         │                                                       │
│         ▼                                                       │
│  3. Diffing: Compare Old vs New Virtual DOM                     │
│         │                                                       │
│         ▼                                                       │
│  4. Reconciliation: Calculate Minimal Changes                   │
│         │                                                       │
│         ▼                                                       │
│  5. Batch Update: Apply changes to Real DOM                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
Why is this faster?
Direct DOM ManipulationReact’s Approach
Every change = immediate DOM updateChanges batched together
DOM operations are slow (layout, paint)Virtual DOM operations are fast (JS objects)
May update more than neededOnly updates what changed (diffing)

The Reconciliation Algorithm

React’s diffing algorithm has O(n) complexity through these heuristics:
  1. Different element types → Rebuild entire subtree
  2. Same element type → Update only changed attributes
  3. Lists with keys → Efficiently reorder/update items
// Without keys - React rebuilds all list items
<ul>
  {items.map(item => <li>{item}</li>)}
</ul>

// With keys - React tracks each item individually
<ul>
  {items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
Performance Tip: Always use stable, unique key props for list items. Using array index as key can cause bugs when list order changes!

Setting Up a React Project

The easiest way to start a new React project is using Vite (recommended) or Create React App.

Using Vite

npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev

JSX (JavaScript XML)

JSX is a syntax extension for JavaScript. It looks like HTML, but it allows you to write HTML structures within JavaScript code.
const element = <h1>Hello, world!</h1>;

Rules of JSX

  1. Return a Single Root Element: You must wrap adjacent elements in a parent tag or a Fragment (<>...</>).
    // Wrong
    return (
      <h1>Header</h1>
      <p>Paragraph</p>
    );
    
    // Correct
    return (
      <>
        <h1>Header</h1>
        <p>Paragraph</p>
      </>
    );
    
  2. Close All Tags: Self-closing tags must end with a slash (e.g., <img />, <br />).
  3. camelCase Properties: HTML attributes become camelCase in JSX.
    • class -> className
    • onclick -> onClick
    • tabindex -> tabIndex
  4. JavaScript Expressions: You can embed any valid JavaScript expression inside curly braces {}.
    const name = "Josh";
    const element = <h1>Hello, {name}</h1>;
    

Conditional Rendering

You can use JavaScript operators like if or the ternary operator inside JSX.

Ternary Operator

const isLoggedIn = true;

return (
  <div>
    {isLoggedIn ? <button>Logout</button> : <button>Login</button>}
  </div>
);

Logical AND (&&)

Render something only if a condition is true:
const hasNotifications = true;
const count = 5;

return (
  <div>
    {hasNotifications && <span className="badge">{count}</span>}
  </div>
);
Gotcha with &&: Avoid using numbers with &&. If count is 0, React will render 0 instead of nothing!
// ❌ Bad - renders "0" when count is 0
{count && <span>{count} items</span>}

// ✅ Good - use explicit boolean check
{count > 0 && <span>{count} items</span>}

Logical OR (||) and Nullish Coalescing (??)

Provide fallback values:
function Greeting({ name }) {
  return <h1>Hello, {name || 'Guest'}!</h1>;
}

// ?? only checks for null/undefined, not falsy values
function Price({ value }) {
  return <span>${value ?? 'N/A'}</span>;
}

Early Returns

For complex conditions, use early returns for cleaner code:
function UserProfile({ user, loading, error }) {
  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;
  if (!user) return <p>No user found</p>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Styling in React

React offers multiple ways to style components:

Inline Styles

function Button() {
  const buttonStyle = {
    backgroundColor: 'blue',
    color: 'white',
    padding: '10px 20px',
    borderRadius: '5px',
    border: 'none',
    cursor: 'pointer'
  };

  return <button style={buttonStyle}>Click Me</button>;
}
Inline styles use camelCase property names (backgroundColor not background-color) and values are strings or numbers.

CSS Modules

CSS Modules scope styles locally to the component:
/* Button.module.css */
.button {
  background-color: blue;
  color: white;
}

.button:hover {
  background-color: darkblue;
}
import styles from './Button.module.css';

function Button() {
  return <button className={styles.button}>Click Me</button>;
}

Conditional Classes

function Button({ variant, disabled }) {
  const className = `btn ${variant === 'primary' ? 'btn-primary' : 'btn-secondary'} ${disabled ? 'btn-disabled' : ''}`;
  
  return <button className={className}>Click</button>;
}

// Or use a library like clsx
import clsx from 'clsx';

function Button({ variant, disabled }) {
  return (
    <button className={clsx('btn', {
      'btn-primary': variant === 'primary',
      'btn-secondary': variant === 'secondary',
      'btn-disabled': disabled
    })}>
      Click
    </button>
  );
}

Common JSX Pitfalls

1. Forgetting to Return JSX

// ❌ Wrong - forgot to return
function Greeting() {
  <h1>Hello</h1>;
}

// ✅ Correct - explicit return
function Greeting() {
  return <h1>Hello</h1>;
}

// ✅ Correct - implicit return with parentheses
const Greeting = () => (
  <h1>Hello</h1>
);

2. Using class Instead of className

// ❌ Wrong - class is a reserved word in JavaScript
<div class="container">

// ✅ Correct
<div className="container">

3. Forgetting to Close Tags

// ❌ Wrong
<img src="photo.jpg">
<input type="text">

// ✅ Correct
<img src="photo.jpg" />
<input type="text" />

4. Rendering Objects Directly

const user = { name: 'Alice', age: 25 };

// ❌ Wrong - Objects are not valid as React children
<p>{user}</p>

// ✅ Correct - access specific properties
<p>{user.name}, {user.age}</p>

// ✅ Or stringify it
<pre>{JSON.stringify(user, null, 2)}</pre>

5. Incorrect Boolean Attributes

// ❌ Wrong - this will always be disabled
<button disabled="false">Click</button>

// ✅ Correct - pass boolean value
<button disabled={false}>Click</button>
<button disabled={isDisabled}>Click</button>

🎯 Practice Exercises

Create a ProfileCard component that displays:
  • A profile image
  • Name
  • Bio
  • A “Follow” button
// Your solution
function ProfileCard() {
  const user = {
    name: 'Jane Doe',
    avatar: 'https://via.placeholder.com/100',
    bio: 'Full-stack developer passionate about React'
  };

  return (
    <div className="profile-card">
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.bio}</p>
      <button>Follow</button>
    </div>
  );
}
Create a component that shows different status badges based on a status prop:
  • “online” → Green badge
  • “away” → Yellow badge
  • “offline” → Gray badge
function StatusBadge({ status }) {
  const colors = {
    online: '#22c55e',
    away: '#eab308',
    offline: '#6b7280'
  };

  const labels = {
    online: 'Online',
    away: 'Away',
    offline: 'Offline'
  };

  return (
    <span style={{
      backgroundColor: colors[status],
      color: 'white',
      padding: '4px 8px',
      borderRadius: '9999px',
      fontSize: '12px'
    }}>
      {labels[status]}
    </span>
  );
}

// Usage
<StatusBadge status="online" />
Given an array of products, render them as a list:
const products = [
  { id: 1, name: 'Laptop', price: 999 },
  { id: 2, name: 'Phone', price: 699 },
  { id: 3, name: 'Tablet', price: 499 }
];

function ProductList() {
  return (
    <ul>
      {products.map(product => (
        <li key={product.id}>
          {product.name} - ${product.price}
        </li>
      ))}
    </ul>
  );
}

Summary

ConceptDescription
ReactJavaScript library for building component-based UIs
Virtual DOMLightweight representation of the DOM for efficient updates
JSXSyntax extension that lets you write HTML-like code in JavaScript
Babel/SWCCompilers that transform JSX into React.createElement calls
ViteModern, fast build tool for React projects
Fragments<>...</> to group elements without adding DOM nodes
ExpressionsUse {} to embed JavaScript in JSX
Conditional RenderingTernary ? :, logical &&, or early returns

Next Steps

In the next chapter, you’ll learn about Components & Props — how to break your UI into reusable pieces and pass data between them.

Interview Deep-Dive

Strong Answer: The way I think about this is through React’s diffing heuristics. React’s reconciliation algorithm compares the old and new Virtual DOM trees top-down, and for lists, it matches children by their key prop. Without keys (or with index-based keys), React compares by position: old position 0 vs new position 0, old position 1 vs new position 1, and so on. If you insert an item at the beginning, every position now has a “different” element, so React tears down and recreates every single list item — even though the original items are identical, just shifted.With stable keys (like database IDs), React matches old key=“a” to new key=“a” regardless of position. It sees that key=“x” is new and creates it, then moves the existing items by adjusting DOM order. This is the difference between O(n) DOM mutations and O(1) insertion plus some cheap reordering.Where this really bites you in production: if your list items have internal state — say, an open dropdown or an in-progress text input — index keys cause that state to “jump” to the wrong item because React reuses the component instance at position 0 for whatever data is now at position 0. I saw this exact bug on a sortable task board where a user was editing a task name, dragged another task above it, and their edit appeared on the wrong task. The fix was switching from index keys to task.id.Follow-up: How does React’s O(n) diffing algorithm compare to a naive tree diff, and what tradeoffs did the React team make to achieve this?A full tree diff between two arbitrary trees is O(n^3) — essentially comparing every node against every other node, then computing minimal edit operations. That is completely impractical for UI at 60fps. React’s team made two heuristics that bring this down to O(n):First, if two elements have different types (say div vs span, or Header vs Footer), React does not bother diffing their children. It destroys the entire old subtree and builds a new one. This sounds expensive, but in practice, type changes at the same tree position are rare.Second, the key prop lets developers hint at identity across renders so React can match children efficiently without deep comparison. The tradeoff is that developers must choose good keys. If you use Math.random() as a key, you have destroyed the entire benefit and React recreates every item every render — worse than no keys at all.The practical cost of these heuristics is rare false positives: if you swap a div wrapper for a section wrapper, React rebuilds the entire subtree even though the children are identical. But that cost is negligible compared to the O(n^3) alternative.
Strong Answer: JSX is syntactic sugar that compilers like Babel or SWC transform into function calls before the code reaches the browser. Pre-React 17, every JSX element became a React.createElement(type, props, ...children) call, which is why you had to import React from 'react' in every file that used JSX — even if you never referenced React directly. The import was invisible at the source level but required at runtime.React 17 introduced the “new JSX transform” (react/jsx-runtime). Instead of React.createElement, the compiler emits _jsx(type, props) and auto-imports it from react/jsx-runtime. Two practical benefits: you no longer need the React import boilerplate in every file, and the new runtime is slightly more efficient because it avoids the createElement overhead of always extracting key from props at runtime — _jsx handles keys as a separate argument.In terms of what the browser actually sees: <div className="box">Hello</div> becomes something like _jsx("div", { className: "box", children: "Hello" }). Nested elements become nested calls. Fragments become _jsxs(_Fragment, { children: [...] }). The returned objects are plain JavaScript objects describing the Virtual DOM node — they have a type, props, key, and ref. React’s renderer then walks this tree to produce actual DOM nodes.Follow-up: What happens if you use a lowercase component name in JSX like <myComponent />? Why?React treats lowercase JSX tags as native HTML elements and uppercase as React components. If you write <myComponent />, the compiler produces _jsx("myComponent", {}) — passing the string "myComponent" as the element type. The browser will try to render a <mycomponent> HTML element (an unknown element, essentially a generic inline element), not your React component. No error is thrown; it just silently renders the wrong thing, which makes it a tricky bug to diagnose. The fix is always capitalizing component names: <MyComponent /> compiles to _jsx(MyComponent, {}), passing the function reference.This is a compile-time convention, not a runtime check. The Babel/SWC plugin simply looks at the first character’s case to decide whether to emit a string or a variable reference. It is one of those decisions that is simple to implement but occasionally confusing for developers coming from frameworks where component naming is not case-sensitive.
Strong Answer: They are technically correct about the overhead of a single, targeted DOM operation. If you know exactly which element to update and you do element.textContent = 'new value', that is always faster than building a Virtual DOM tree, diffing it, and then doing the same textContent assignment. The Virtual DOM adds CPU work that a surgical manual update does not need.But that argument misses the real problem the Virtual DOM solves: developer productivity and correctness at scale. In a real application with hundreds of components, state flowing through many layers, and UI that depends on multiple data sources, manually figuring out the minimal set of DOM operations for every state change is error-prone and maintenance-heavy. You end up with bugs where the DOM is out of sync with the data, or you over-update (causing layout thrashing), or you forget to clean up listeners.The Virtual DOM’s real value is that it lets you write declarative code — “given this state, render this UI” — and React figures out the efficient delta. In benchmarks, React’s batched updates often outperform naive imperative approaches because it coalesces multiple state changes into a single DOM write. For example, if three separate state updates happen in the same event handler, React batches them into one re-render and one DOM commit rather than three separate layout-triggering writes.That said, for extreme performance scenarios — canvas rendering, animations at 120fps, or very large virtualized lists — the Virtual DOM can become a bottleneck. That is why libraries like Svelte compile away the Virtual DOM entirely and generate direct DOM update instructions. And it is why React introduced concurrent features and the Fiber architecture: to make the reconciliation interruptible so that high-priority updates (user input) are not blocked by low-priority ones (background data).Follow-up: How does React Fiber change the reconciliation model compared to the original stack reconciler?The original stack reconciler processed the entire component tree synchronously in one call stack. If you had 1000 components to reconcile, the main thread was blocked until all 1000 were processed. During that time, the browser could not handle user input, animations froze, and the app felt janky.Fiber replaces the single recursive walk with an interruptible linked-list data structure. Each Fiber node represents a component and links to its child, sibling, and parent. React can pause work on one branch, handle a higher-priority update (like a keystroke), and resume where it left off. This is the foundation of concurrent rendering, startTransition, and Suspense.In practice, this means React can break a large reconciliation into small chunks spread across multiple animation frames, keeping the app responsive even during heavy updates. The tradeoff is increased internal complexity — the Fiber data structure and the two-phase commit (render phase vs commit phase) are considerably more complex than the old stack reconciler. But that complexity is internal to React; as a developer, you write the same declarative components.
Strong Answer: This is one of the most common categories of React bugs, and I have a mental checklist I work through:First, state mutation. If someone does state.items.push(newItem); setState(state.items), React sees the same array reference and skips the re-render. The fix is always creating a new reference: setState([...state.items, newItem]). This is the single most common cause I have seen in production codebases, especially with nested objects where someone does state.user.name = 'new' instead of spreading.Second, stale closures. If a useEffect or event handler captures an old value of state because the dependency array is wrong, the handler runs with outdated data. The UI might update but the handler logic uses the old value. I check this by logging inside the handler and comparing with the React DevTools state.Third, the component is not actually subscribed to the state that changed. For Context, this means the component is not inside the Provider, or the custom hook is reading from a different context instance. For Redux, the selector might be returning a memoized value that has not changed despite the store updating.Fourth, React.memo with a custom comparator that is too aggressive — it returns true (props are equal) when they are actually different, so the component never re-renders.Fifth, the state update might be correct but the derived value displayed in the UI is computed incorrectly. I have seen cases where useMemo has a stale dependency array, so the memoized value is never recomputed.My systematic approach: React DevTools is step one. I check whether the state actually changed in the Components panel. If it did, I check whether the component re-rendered using the Profiler. If it re-rendered but the DOM did not update, the issue is in the JSX logic (conditional rendering, wrong variable). If it did not re-render, I trace upward to find where the re-render chain breaks — usually a memo boundary or a Context that is not updating.Follow-up: How would you detect and fix a stale closure bug in a useEffect that fires a setInterval?Classic scenario: you have an interval that reads count but the dependency array is empty, so the closure captures the initial count value forever.Detection: add a console.log(count) inside the interval callback and watch it always log the same value even after clicking increment. Or use the React DevTools Profiler to confirm the component re-renders with the correct state but the interval callback is stuck on an old value.Fix: use the functional updater form setCount(c => c + 1) instead of setCount(count + 1). The functional form does not read count from the closure — it receives the current value as an argument from React. Alternatively, store count in a ref and read countRef.current inside the interval, but the functional updater is cleaner for state updates. For cases where you need to read state without updating it (like logging), a ref is the right tool.