ReactJS Overview
The complete mental model of React: what it is and isn't, the declarative UI = f(state) idea, elements vs components, the Virtual DOM and reconciliation, render vs commit, one-way data flow, and where React fits in the modern ecosystem — beginner to advanced in one page.
What React actually is. React is a JavaScript library (not a full framework) for building user interfaces out of components. It does one job extremely well: keep the screen in sync with your data. It deliberately leaves routing, data fetching, and global state to other libraries — which is why you compose React with tools like React Router, TanStack Query, or Next.js. Understanding this scope keeps you from expecting React to do things it was never meant to do.
The one idea that matters: UI = f(state). React's core insight is that the UI should be a pure function of state. You describe what the screen should look like for a given set of data; you never write step-by-step instructions to mutate the DOM. Change the data, and React recomputes the UI and updates the screen. This is declarative programming — you declare the destination, not the route.
Declarative vs imperative — feel the difference. Imperative (vanilla DOM): find the node, read its current text, decide what changed, set innerHTML, toggle a class, remove a child. You manage every transition by hand and bugs hide in the transitions. Declarative (React): return <p>{count}</p> and let React figure out the difference between the old and new output. You stop describing how and only describe what — the entire class of 'I forgot to update the DOM in one place' bugs disappears.
Elements vs components (a distinction interviewers love). A component is a function you write that returns markup. An element is the lightweight plain-object description that calling that component (or writing JSX) produces — { type: 'h1', props: { children: 'Hi' } }. Elements are cheap, immutable snapshots; React creates thousands per render and throws them away. Components are the reusable factories; elements are their per-render output.
The Virtual DOM — what it is. The Virtual DOM is a lightweight in-memory tree of those element objects representing what the UI should be. It is not a faster DOM; it is a description of the DOM. Its value is that comparing two plain-object trees in memory is cheap, whereas touching the real DOM (which can trigger layout and paint) is expensive. So React does the expensive work rarely and the cheap work often.
Reconciliation — how updates happen. When state changes, React re-runs the component to produce a new Virtual DOM tree, then diffs it against the previous tree. This diffing process is called reconciliation. React uses heuristics: same element type → reuse and update the DOM node; different type → tear down and rebuild; lists → use keys to match items. The result is the minimal set of real DOM operations, applied in one batch.
Render phase vs commit phase. A React update has two phases. The render phase calls your components to compute the new tree and diff it — this must be pure and can be paused or restarted by React. The commit phase applies the computed changes to the real DOM and runs effects — this happens synchronously and only once per update. Knowing these two phases explains why render must have no side effects: React may run it more than once.
One-way data flow. Data in React flows in one direction: down the component tree, from parent to child via props. A child never reaches up to mutate a parent. To change something upward, a child calls a callback the parent passed down. This unidirectional flow is what makes a large React app tractable — you can always trace where a value came from by walking up the tree.
The Fiber architecture (advanced context). Since React 16, the reconciler is called Fiber. It breaks rendering into small units of work that can be interrupted, prioritized, and resumed, enabling features like concurrent rendering, useTransition, and Suspense. You rarely touch Fiber directly, but it's why modern React can keep the UI responsive during heavy updates — it can pause low-priority rendering to handle a click.
Common gotchas. React re-renders a component when its state or props change, and by default re-renders its children too — a re-render is not automatically a DOM update (reconciliation may find nothing changed), so 'React re-rendered' does not mean 'the DOM changed.' Mutating state directly bypasses React's change detection and leaves the UI stale. And the Virtual DOM is not magic performance — a poorly-structured tree can still be slow; correctness first, then optimize.
The mental model (memorise this). You write components (functions) → calling them produces elements (a Virtual DOM description) → on every state change React re-renders that description, diffs it against the last one (reconciliation) → and commits only the minimal real-DOM changes. UI = f(state), data flows down, events flow up. Get those five sentences and the rest of React is just API.
Think of a React component tree as a rendering/templating layer re-evaluated whenever your domain model changes — like a Vert.x/Spring handler that returns a fresh view for the current model rather than patching a shared mutable page. The Virtual DOM diff is a change-detection engine, much like comparing two JPA entity snapshots and issuing UPDATEs only for the dirty columns instead of rewriting every row. One-way data flow mirrors passing an immutable request/DTO down through service methods: callees read it, they don't mutate the caller's state. Render-phase purity is like a function that must be free of side effects so the framework can retry it safely — the commit phase is where the transaction actually flushes.
- React is a UI library, not a framework: it syncs the screen to your data and leaves routing, fetching, and global state to other tools.
- The whole model is UI = f(state): you declare what the screen should be for the current data; React computes how to get there.
- A component is the function you write; an element is the cheap immutable object it returns describing the UI.
- The Virtual DOM is a description of the DOM, not a faster DOM; its value is that in-memory diffing is cheaper than touching the real DOM.
- Reconciliation diffs the new element tree against the old one and applies the minimal batched set of real DOM changes.
- Rendering has two phases: render (pure, interruptible, computes the diff) and commit (synchronous, applies changes and runs effects).
- Data flows down via props; events flow up via callbacks — this one-way flow makes large apps traceable.
- A re-render is not the same as a DOM update: React may re-run a component and find nothing changed to commit.
- The Fiber reconciler splits work into interruptible units, enabling concurrent features like useTransition and Suspense.
- Correctness first: the Virtual DOM does not make a badly structured component tree fast on its own.
Worked Code
// IMPERATIVE (vanilla): you manage every transition by hand.
const btn = document.querySelector('#like');
let liked = false;
btn.addEventListener('click', () => {
liked = !liked; // mutate state
btn.textContent = liked ? 'Liked' : 'Like'; // manually sync the DOM
btn.classList.toggle('active', liked); // and again, everywhere
});
// DECLARATIVE (React): describe the UI for the current state; React syncs.
import { useState } from 'react';
function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<button
className={liked ? 'active' : ''}
onClick={() => setLiked(prev => !prev)} // just change the data
>
{liked ? 'Liked' : 'Like'} {/* UI = f(state) */}
</button>
);
}// A COMPONENT is a function you write.
function Greeting() {
return <h1 className="title">Hello</h1>;
}
// The JSX above compiles to a call that returns an ELEMENT —
// a plain, immutable object DESCRIBING the UI (not real DOM):
const element = {
type: 'h1',
props: { className: 'title', children: 'Hello' },
};
// React creates thousands of these per render, diffs them, and
// throws them away. They are cheap descriptions, not DOM nodes.import { createRoot } from 'react-dom/client';
import App from './App';
// React controls ONE DOM subtree from here down. Everything inside
// <App /> is described declaratively; React reconciles it into #root.
const container = document.getElementById('root')!;
const root = createRoot(container);
root.render(<App />);
// From now on you never call container.innerHTML = ... again.
// You change state inside App, and React updates #root for you.import { useState } from 'react';
function Clock() {
const [now, setNow] = useState(Date.now());
// Even if this component RE-RENDERS every tick, React only
// COMMITS the text nodes that actually changed. Re-render (render
// phase) recomputes the description; commit phase touches the DOM.
return (
<div>
<span>Static label</span> {/* unchanged -> no DOM write */}
<time>{new Date(now).toLocaleTimeString()}</time> {/* changes */}
</div>
);
}▶Try It Live
Edit the code and press Run — it executes safely in a sandboxed iframe. Use the Console tab for log output.
Interview-Ready Q&A
Directly mutating the real DOM can trigger synchronous layout and paint, and doing many imperative mutations is hard to reason about. React keeps an in-memory tree of element objects describing the desired UI, re-renders it on state change, diffs it against the previous tree (reconciliation), and applies the minimal set of real DOM changes in one batch. The win is a declarative model plus batching and minimal updates — not that the DOM is intrinsically slow.
- 1UI = f(state): describe the result for current data; React figures out the DOM changes.
- 2React is a library, not a framework — compose it with routing/data/state tools.
- 3Component = function you write; element = cheap immutable object it returns.
- 4The Virtual DOM is a description of the DOM; in-memory diffing is what's cheap.
- 5Reconciliation diffs new vs old element tree and batches minimal real DOM writes.
- 6Two phases: render (pure, interruptible) then commit (DOM writes + effects).
- 7Data flows down via props; events flow up via callbacks (one-way data flow).
- 8A re-render is not automatically a DOM update.
- 9Never mutate state in place — it bypasses change detection and leaves the UI stale.
- 10Fiber makes rendering interruptible, enabling concurrent React features.