text
$ du -sh node_modules
428M node_modules
$ top -p $(pgrep -d ‘,’ node)
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12842 silas 20 0 1.244g 312.4m 45.2m S 4.2 1.9 0:18.42 node
I have spent the last three decades working with the 8051, the AVR, and various ARM Cortex-M cores. I am accustomed to fighting for every single byte of SRAM. I have written bootloaders that fit into 512 bytes of flash. I have optimized interrupt service routines to execute in under twenty clock cycles because the timing of a motor controller depends on it. Yesterday, my CTO informed me that our legacy industrial control interface—a rock-solid, C-based serial protocol that has functioned without a single bit-flip since 1998—is "outdated." He wants a "modern, responsive web dashboard."
He told me to look into "React."
After forty-eight hours of staring into this abyss, I have come to the realization that the modern web is not a feat of engineering; it is a house of cards built atop a landfill. We have traded deterministic execution and memory safety for a bloated, non-deterministic stack of abstractions that would make a mainframe cry. I have been asked to document my findings. Here is my report on the absolute state of "modern" software development.
## What is React? A Layer of Grease on a Broken Axle
To answer the question "what is react" is to describe a solution to a problem that should never have existed. In the embedded world, if I want to update a display, I write a value to a memory-mapped I/O register. The hardware reflects that change instantly. In the browser, you have the Document Object Model (DOM). The DOM is a tree structure so inefficient and slow that developers had to invent a "Virtual" version of it just to keep the UI from stuttering like a damaged stepper motor.
React v18.3.1 is, at its core, a "declarative" library. In the vocabulary of people who don't have to worry about stack overflows, "declarative" means "I will tell the computer what I want, and I will pray the library figures out the most efficient way to do it." As an engineer, I find this offensive. I don't want to "declare" my state; I want to manage it. React sits between the developer and the browser, intercepting every change and running a "reconciliation" algorithm that consumes more CPU cycles than a PID loop running at 10kHz.
It is a dependency nightmare. To get a "Hello World" running in Node v20.11.0, I had to download 428 megabytes of JavaScript files. For context, the entire Apollo 11 guidance computer source code is about 40 kilobytes. We are using half a gigabyte of disk space to render a text string in a browser. We have lost our way.
## The Virtual DOM: Solving Problems We Didn't Have
The "Virtual DOM" is the primary selling point of React. The marketing—which I will ignore—claims it makes updates fast. Let’s look at the reality. When a piece of "state" changes, React creates an entirely new virtual tree of the UI. It then compares this new tree with the old tree (a process called "diffing") to find out which parts of the actual DOM need to be updated.
Imagine if I did this in an embedded system. Imagine if, to turn on a single LED on a control panel, I first had to create a virtual map of every LED, compare it to the previous map, calculate the difference, and then finally send the command to the GPIO port. The watchdog timer would reset the system before I even finished the comparison.
This "reconciliation" is a massive waste of resources. It is an admission that the underlying platform (the browser) is too slow to handle direct manipulation. Instead of fixing the platform, we’ve added a massive, memory-hungry middleman. They call it "efficient." I call it a resource leak disguised as a feature.
## JSX: A Syntax Error Disguised as a Feature
In C, we have a clear separation between logic and data. In React, they use something called JSX. It looks like HTML, but it lives inside your JavaScript. It’s a pre-processor’s nightmare. You are essentially writing tags inside your logic, which then get transpiled by something called Babel into a series of `React.createElement()` calls.
Why? Why are we mixing concerns? If I put assembly instructions inside a string in a C file and expected the compiler to just "figure it out," I’d be fired. But in the web world, this is considered "best practice." It makes the code unreadable and requires a massive build pipeline just to turn it into something the browser can actually execute.
## Code Comparison 1: State Management
Let’s look at how we handle a simple toggle switch.
**The C Way (Embedded):**
```c
// Direct, deterministic, uses 1 bit of memory.
volatile uint8_t system_flags;
#define PUMP_BIT 0
void toggle_pump() {
system_flags ^= (1 << PUMP_BIT);
update_hardware_output(system_flags);
}
The React Way (Hooks):
// Indirect, non-deterministic, involves closures, objects, and garbage collection.
import React, { useState } from 'react';
function PumpControl() {
const [isPumpOn, setIsPumpOn] = useState(false);
const togglePump = () => {
setIsPumpOn(prevState => !prevState);
};
return (
<button onClick={togglePump}>
{isPumpOn ? 'Stop Pump' : 'Start Pump'}
</button>
);
}
In the C version, I know exactly where that bit is. I know when it changes. In the React version, useState triggers a re-render of the entire component. The togglePump function is re-created on every render unless I wrap it in another abstraction called useCallback. We are generating garbage for the Garbage Collector to clean up every time a user clicks a button. It’s a disgrace.
Table of Contents
The State of “State”: Race Conditions by Design
React manages “State,” but it does so asynchronously. When you call setState, it doesn’t happen immediately. It’s “scheduled.” For someone who deals with real-time interrupts, this is terrifying. You have no guarantee of when the UI will reflect the actual state of the system.
In a control system, if the pressure sensor hits a limit, I need the relief valve to open now. I don’t want the valve to open “at the next available animation frame” or “whenever the reconciliation algorithm finishes diffing the fiber tree.” React’s state management is built for social media feeds, not for engineering. It introduces a layer of latency that is inherent to its design.
Furthermore, the “State” is often duplicated across the entire application. You have local state, “Context” state, and often a third-party library like Redux for “Global” state. It’s a recipe for race conditions. I’ve seen React apps where the UI shows a “Loading” spinner while the underlying data has already arrived, simply because a “hook” didn’t trigger correctly.
Code Comparison 2: Side Effects and Interrupts
In embedded, we use interrupts for side effects. It’s clean. It’s hardware-level.
The C Way:
// Hardware triggers this when data arrives on the UART
void __attribute__((interrupt)) UART_RX_ISR() {
uint8_t data = UDR0;
process_incoming_byte(data);
}
The React Way (useEffect):
// This runs... eventually. Maybe too often. Maybe not enough.
useEffect(() => {
const socket = new WebSocket('ws://control-system.local');
socket.onmessage = (event) => {
setData(JSON.parse(event.data));
};
return () => socket.close();
}, []); // Empty dependency array means "run once," unless it doesn't.
The useEffect hook is React’s answer to lifecycle management. It is notoriously difficult to get right. If you forget a dependency in the array, your effect uses stale data. If you include too many, it runs in an infinite loop, pegging the CPU at 100% and crashing the browser tab. It’s a high-level abstraction that manages to be more dangerous than a raw pointer.
The Build Pipeline: A Rube Goldberg Machine
To run a React application, you don’t just “run” it. You have to build it. This involves:
1. NPM/Yarn: To fetch thousands of dependencies.
2. Babel: To transpile JSX and modern JS into older JS.
3. Webpack/Vite: To bundle these thousands of files into a few massive ones.
4. Minifiers: To strip out the whitespace because the code is so bloated it won’t load otherwise.
In my world, a “build” takes three seconds and produces a .hex file. In the React world, a build takes three minutes, consumes 2GB of RAM, and produces a “dist” folder that is larger than the entire operating system I’m using to write this report.
I looked at the package-lock.json file. It is 14,000 lines long. It contains references to packages like is-odd and is-number. Have we forgotten how to use the modulo operator? Why does a UI library need a dependency tree that looks like a map of the human genome?
Agonizing Detail: The Boilerplate File Structure
When you run npx create-react-app, it generates a directory structure that is an affront to minimalism. Let’s break down the “Standard” boilerplate:
node_modules/: A black hole containing 30,000 files. This is where your disk space goes to die. It contains everything from testing frameworks to utility functions that check if a string is empty.public/:index.html: The only real file in the whole mess. It’s usually empty, containing a single<div id="root"></div>. React then injects the entire application into this div like a parasite.favicon.ico: An icon.manifest.json: Metadata for “Progressive Web Apps,” a term that implies the web is normally regressive.
src/:App.css: Global styles. Because we can’t just use standard CSS, we have to import it into a JavaScript file.App.js: The “root” component. Usually a mess of JSX.App.test.js: A file for testing, which most developers seem to ignore based on the quality of the web today.index.css: More styles.index.js: The entry point. This is whereReactDOM.createRootis called.reportWebVitals.js: A script to measure how slow your app is. The irony is palpable.setupTests.js: More configuration for the testing suite.
Every one of these files has its own set of dependencies. Every one of these files adds to the cognitive load. In a C project, I have main.c, hardware.c, hardware.h, and a Makefile. I can hold the entire architecture in my head. With React, I need a map and a flashlight just to find where the button click is handled.
Code Comparison 3: Conditional Rendering
The C Way:
if (temperature > 100) {
display_alert("OVERHEAT");
} else {
display_status("OK");
}
The React Way:
return (
<div>
{temperature > 100 ? (
<AlertComponent message="OVERHEAT" />
) : (
<StatusComponent message="OK" />
)}
</div>
);
While the React code looks “cleaner” to some, it’s deceptive. AlertComponent and StatusComponent are functions. In the React version, both branches of that ternary operator involve function calls, object creation, and potential re-renders of child components. In C, it’s a simple branch instruction at the CPU level. The overhead of the React approach is orders of magnitude higher for the exact same result.
The “Hooks” Nightmare: useMemo and useCallback
Because React is so inefficient at managing its own rendering, it provides “optimization” hooks like useMemo and useCallback. These are essentially manual cache management.
If you have a heavy calculation, you wrap it in useMemo so React doesn’t re-run it on every render. But wait—you have to manually manage the dependency array for that cache. If you miss one, your cache is stale. If you include a new object, the cache is busted anyway because of how JavaScript handles object equality.
This is what happens when you try to hide the reality of the machine from the developer. You end up creating a new, more complex set of rules that the developer has to follow to prevent the abstraction from collapsing. It’s like trying to fix a leaky pipe by wrapping it in a second, larger pipe that also leaks.
Tailwind and CSS-in-JS: The Final Insult
As if the JavaScript wasn’t enough, we now have “Tailwind CSS.” Instead of writing CSS in a CSS file, you write “utility classes” directly in your JSX.
className="flex items-center justify-between p-4 bg-blue-500 text-white rounded-lg shadow-md"
This is just inline styling with more steps. It turns the HTML (or JSX) into an unreadable wall of text. It’s the equivalent of writing a C program where every variable name is a description of its memory address and its color on a debug screen. It’s a rejection of the “Separation of Concerns” principle that we’ve spent decades refining.
And then there’s “Styled Components,” where you write your CSS inside a JavaScript template literal. So now you have CSS inside JS inside HTML-like tags. It’s a nesting doll of bad ideas. The build tool then has to parse all of this, extract the CSS, and inject it back into the DOM at runtime. The CPU usage on my laptop spikes just thinking about it.
The Reconciliation Algorithm: A Deeply Inefficient Register Update
Let’s talk about “Fiber.” React v16 introduced the Fiber architecture to allow for “concurrent rendering.” This means React can pause its diffing algorithm to let the browser do something else, like handle a mouse click.
In the embedded world, we call this “preemptive multitasking,” and we’ve had it since the 1970s. React has implemented a software-level scheduler to manage its own bloat. It’s a complex system of linked lists and work loops designed to hide the fact that the reconciliation process is too heavy to run in a single pass without freezing the UI.
When you update a “prop” (a read-only variable passed to a component), React marks that component as “dirty.” It then traverses the tree, checking every child to see if it also needs to be updated. This is a recursive process that, even with the “Fiber” optimizations, is incredibly taxing compared to a simple memory write. We are using a supercomputer to do the work of a pocket calculator, and we’re doing it badly.
Memory Leaks and the “State” of the Art
JavaScript is garbage-collected, which gives web developers a false sense of security. In React, it is incredibly easy to create memory leaks. If you set up a setInterval in a useEffect and forget to return a cleanup function, that interval will run forever, even after the component is unmounted.
Because everything in React is a closure, these intervals and event listeners keep references to the component’s state and props, preventing the Garbage Collector from freeing that memory. I’ve seen “modern” web dashboards that consume 2GB of RAM after being left open for a few hours. A 64KB microcontroller would have crashed in seconds, but because modern PCs have 32GB of RAM, we just ignore the leak. It’s lazy engineering.
Final Thoughts on “What is React”
What is React? It is a monument to our own excess. It is a library that provides a “declarative” interface by consuming vast amounts of memory and CPU cycles to manage a DOM that was never designed for high-performance UIs. It is a system that requires a 400MB toolchain to produce a 2MB bundle to display 10KB of data.
It is the opposite of everything I have learned in thirty years of engineering. It is non-deterministic, it is opaque, and it is fragile. The CTO says we need it for “developer productivity.” I say if your developers need 428MB of dependencies to be productive, you have the wrong developers.
I am going back to Vim. I have a bootloader to write, and I only have 128 bytes of overhead left. I don’t have room for a Virtual DOM, and frankly, I don’t need one. If the “modern web” is the future, I’m glad I’m nearing retirement.
// Real code. No hooks. No fibers. No bloat.
void main() {
init_hardware();
while(1) {
if (data_ready) {
process_data();
}
sleep_mode();
}
}
Done. I’m turning off this Node server before my fan takes flight.
Related Articles
Explore more insights and best practices: