what is react – Guide

INCIDENT REPORT: DOM_CORRUPTION_0x042
TIMESTAMP: 03:14:22 AM
STATUS: CRITICAL / MIGRATION IN PROGRESS
ENGINEER: Senior Systems Lead (Systems Purist)

The following script was found at the heart of the failure. It is a standard imperative approach to updating a dashboard. It failed because the network latency on the GET /api/v1/telemetry call caused a race condition where the updateDashboard function attempted to write to a DOM node that had been unmounted by a separate navigation event.

// DEPRECATED IMPERATIVE JUNK - DO NOT USE
function updateDashboard() {
    const container = document.getElementById('telemetry-root');
    fetch('/api/v1/telemetry')
        .then(res => res.json())
        .then(data => {
            // Race condition: if the user navigated, 'container' is now null or detached
            if (container) {
                const list = document.createElement('ul');
                data.metrics.forEach(m => {
                    const li = document.createElement('li');
                    li.innerText = `${m.name}: ${m.value}`;
                    list.appendChild(li);
                });
                container.innerHTML = ''; // Heavy layout thrashing
                container.appendChild(list);
            }
        })
        .catch(err => console.error("System Failure", err));
}

At 3:15 AM, the CTO pings me. He’s looking at a blank screen and a stack trace that looks like a recursive nightmare. He asks: “What is React, and why are we paying 40kb of gzipped overhead to fix a null pointer check?”

I didn’t have the heart to tell him it’s actually closer to 130kb uncompressed once you include the scheduler and the DOM shim. Here is the post-mortem.


1. The Reconciliation Tax: Virtual DOM vs. Reality

To understand what is React, you have to understand that it is essentially a massive, heap-allocated buffer for the DOM. In the old days, we wrote to the framebuffer or the terminal directly. If you wanted to change a pixel, you changed a byte. The modern web, however, treats the DOM as a protected kernel space that is incredibly expensive to access. Every time you touch innerHTML, the browser’s layout engine (Blink or WebKit) has to recalculate styles, compute geometries, and repaint.

React 18.3.1 doesn’t solve the “expensive DOM” problem; it abstracts it behind a “Virtual DOM.” This is a tree of JavaScript objects (Plain Old JavaScript Objects, or POJOs) that mirrors the actual DOM. When state changes, React creates a new virtual tree and compares it to the old virtual tree.

This is called “Reconciliation.”

The cost is significant. Instead of one tree, you now have two trees in memory, plus the overhead of the “Diffing Algorithm.” React uses a heuristic O(n) algorithm because a true tree-diffing algorithm is O(n³). If we had 1,000 nodes, an O(n³) algorithm would require a billion comparisons. React’s O(n) approach assumes that if a component’s type changes (e.g., from a <div> to a <span>), the entire subtree is discarded. It’s a brutal, memory-heavy shortcut that we’ve accepted because we can’t be trusted to manage pointers manually anymore.

$ npm list react
[email protected]
└── [email protected]

$ ls -lh node_modules/react-dom/umd/react-dom.production.min.js
-rw-r--r--  1 purist  staff   131K May 15 12:00 react-dom.production.min.js

We are loading 131KB of JavaScript just to decide which <li> tag needs to change. In C, I could update a telemetry display with a 4KB binary. This is the “Reconciliation Tax.”

2. Fiber: The Scheduler We Didn’t Ask For

If you look at the internals of React 18.3.1, you’ll find the “Fiber” architecture. To a systems engineer, Fiber is essentially a re-implementation of the operating system’s thread scheduler, but written in a single-threaded, high-level language.

Before React 16, the reconciliation process was “stack-based.” Once it started updating the DOM, it couldn’t stop. If you had a large list to render, the main thread would hang, the UI would freeze, and the user’s mouse movements would be ignored.

Fiber changed this by breaking the work into “units of work” (the Fiber nodes). Each Fiber is a linked list node that keeps track of its parent, child, and sibling. This allows React to pause the rendering process, yield control back to the browser to handle an input event, and then resume where it left off.

It’s a cooperative multitasking system. But let’s be clear about what is happening under the hood: we are manually managing a call stack on the heap because we don’t trust the browser’s own event loop to prioritize UI updates correctly.

The Fiber structure looks something like this in memory:

// Conceptual C representation of a Fiber node
struct Fiber {
    int tag;                // Type of component
    void* stateNode;        // Reference to actual DOM node
    struct Fiber* return;   // Parent
    struct Fiber* child;    // First child
    struct Fiber* sibling;  // Next sibling
    int effectTag;          // Bitmask for side effects (Update, Deletion, etc.)
    void* memoizedState;    // The hook linked list
};

Every single element in your React application is one of these structs. If you have a dashboard with 2,000 cells, you have 2,000 of these objects being tracked, traversed, and garbage-collected.

3. From Classes to Hooks: A Paradigm Shift in Memory Management

We moved from Class components to Functional components with Hooks. The marketing says it’s for “readability.” The technical reality is that it changed how we allocate memory.

In Class components, every component was an instance of a class. This meant a this context, which is notoriously difficult for JavaScript engines to optimize and for developers to manage without creating memory leaks via unbound methods.

Hooks (introduced in 16.8 and refined in 18.3.1) replaced instances with closures. But there’s a catch. Since there is no “instance” to store state, React stores the state for a component on the Fiber node itself, in a linked list of “hook” objects.

This is why you cannot call hooks inside loops or conditions. The “Rules of Hooks” exist because React relies on the order of execution to find the correct state. If you have useState followed by useEffect, React looks at the first node in the linked list for the state and the second node for the effect. If you skip one, the pointers are misaligned, and you’re reading state from the wrong memory address. It’s a pointer arithmetic nightmare disguised as a “cleaner API.”

4. Synthetic Events and the Abstraction Overhead

React doesn’t use standard browser events. If you attach an onClick handler to a button, you aren’t actually attaching an event listener to that DOM node. Instead, React uses “Synthetic Events.”

In version 18.3.1, React attaches a single event listener to the root of your application (the div#root). When you click a button, the event bubbles up to the root, React intercepts it, wraps the native browser event in a SyntheticEvent wrapper, and then dispatches it to your function.

Why? Cross-browser consistency. But the cost is more heap allocation for every single click. In older versions, React tried to be clever with “Event Pooling,” where it would reuse the same SyntheticEvent object to save on garbage collection. They had to remove it in React 17 because it was too confusing for developers who tried to use the event asynchronously.

So now, every interaction creates a new object that must be tracked and eventually collected. We’ve traded CPU cycles and memory pressure for the convenience of not having to worry about how Internet Explorer 11 (which is dead anyway) handles event bubbling.

5. Concurrent Rendering and the useSyncExternalStore Necessity

The most complex part of React 18.3.1 is “Concurrent Mode.” This allows React to prepare multiple versions of the UI at the same time. It can start rendering a “heavy” update in the background while keeping the current UI responsive.

However, this introduces a classic systems problem: Tearing.

Tearing occurs when a component reads from an external data store (like a global variable or a Redux store) and that store changes while React is in the middle of a paused render. Half of your UI might show the old data, and the other half shows the new data.

To solve this, React introduced useSyncExternalStore. This hook is a 300-word explanation in itself. Essentially, it forces a synchronous re-render if the external store changes during a transition. It’s a “bail-out” mechanism.

Think about the irony: we spent years building a concurrent, asynchronous rendering engine, only to realize that global state (which we all use) is fundamentally incompatible with it. So we had to write a specific hook to disable the concurrency for external data.

useSyncExternalStore takes a subscribe function, a getSnapshot function, and an optional getServerSnapshot. It registers a listener to the store. If the store’s value changes, React checks the “snapshot” (a versioned pointer to the data). If the snapshot has changed since the render started, React throws away the work it was doing and starts over. It’s a retry-loop for UI consistency.

This is the level of complexity we have reached. We are implementing versioning and snapshot isolation—concepts from database engine design—just to make sure a “Username” label matches the “User Profile” picture on a webpage.

6. The Cost of Abstraction: Build Sizes and Runtime Bloat

Let’s look at the “High-Stakes Migration” telemetry. When we switched from the imperative script to a full React 18.3.1 implementation, our memory footprint on the client-side jumped significantly.

USER       PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
purist    4012  12.5  4.2  6124512 312420   ??  S    03:20AM   0:14.22 Chrome Helper (Renderer)

312MB of RSS (Resident Set Size) for a dashboard. A significant portion of that is the React Fiber tree and the associated closures.

So, what is React in the final analysis?

It is a runtime library that provides a declarative abstraction over a mutable, stateful DOM. It treats the UI as a pure function of state: UI = f(state).

The benefits are undeniable:
1. Predictability: We no longer have the “3:00 AM race condition” where a DOM node is null because we have a centralized state-to-UI pipeline.
2. Componentization: We can encapsulate logic into discrete units, making the “house of cards” slightly more modular.
3. Developer Velocity: We can hire developers who don’t know what a pointer is, and they can still build a functioning (albeit slow) interface.

But the costs are hidden in the “Execution Overhead.” Every time a user types a character in a text box, React may trigger a re-render of the entire component tree, perform a diff of thousands of objects, and execute a series of “Effects.”

We have replaced simple logic with a massive engine that requires constant tuning. We use useMemo and useCallback to prevent React from re-allocating functions on every render—essentially doing manual memory management within a garbage-collected language. We use useTransition to tell the scheduler which updates are “non-urgent,” which is just a high-level way of saying “don’t block the UI thread with this junk.”

The Post-Mortem Summary

The 3:00 AM outage was resolved by wrapping the telemetry update in a useEffect hook. The race condition is gone because React handles the cleanup. If the component unmounts, the “Effect” is cancelled, and the state update is discarded.

// THE REACT "FIX"
import { useState, useEffect } from 'react';

function TelemetryDashboard() {
    const [data, setData] = useState(null);

    useEffect(() => {
        let isMounted = true;
        fetch('/api/v1/telemetry')
            .then(res => res.json())
            .then(json => {
                if (isMounted) setData(json);
            });
        return () => { isMounted = false; }; // Manual cleanup flag
    }, []);

    if (!data) return <div>Loading...</div>;

    return (
        <ul id="telemetry-root">
            {data.metrics.map(m => (
                <li key={m.id}>{m.name}: {m.value}</li>
            ))}
        </ul>
    );
}

We fixed the bug. But in doing so, we pulled in 131KB of code, established a complex Fiber-based scheduling system, and increased our heap usage by 40%.

What is React? It is the white flag of surrender for web development. It is the admission that the DOM is too hard to manage, that state is too hard to track, and that we would rather throw CPU cycles at the problem than write precise, imperative code. It is a sophisticated, over-engineered, and occasionally brilliant solution to a problem we created for ourselves by building applications inside a document viewer.

It works. But as a systems purist, I can still hear the CPU fans spinning from here. It’s the sound of a thousand Fiber nodes being reconciled at 60 frames per second, and it sounds like inefficiency.

Related Articles

Explore more insights and best practices:

Leave a Comment