Skip to main content

Build Your Own Front-end Framework (React-like)

"React is just a function that takes state and returns a tree of components." — almost true

Building a React-like framework demystifies the abstraction that powers most of the modern web. By the end you understand: the virtual DOM, reconciliation, hooks, fibers, and why React 18 added concurrent rendering. ~500 lines of JavaScript.


1. Overview & motivation

A React-like framework consists of:

  1. JSX → calls — JSX desugars to createElement(type, props, children). (Or you skip JSX and call h(...) directly.)
  2. Virtual DOM — JS objects representing the desired UI tree.
  3. Reconciliation — diff the new VDOM against the old; produce a minimal patch.
  4. Rendering — apply the patch to real DOM.
  5. Hooks / stateuseState, useEffect for components to own state.

What you can only learn by building one:

  • Why the virtual DOM is just a JS object — there's nothing mysterious about it.
  • Why reconciliation is the heart of React (and why React's algorithm has tradeoffs that, e.g., Solid.js rejects).
  • Why hooks have rules ("only call hooks at the top level") — they're stored in an array indexed by call order.
  • Why fiber was introduced — interruptible rendering for responsiveness.

2. Where this fits in the degree

  • Phase: Production
  • Semester: 8 (System Design and Leadership) or as a capstone elective
  • Modules deepened: Sem 8 Module 1 (system design methodology) — API design forces explicit decisions.

Cross-phase relevance:


3. Prerequisites

  • JavaScript fluency. Understand closures, hoisting, this, and ES module syntax.
  • Familiarity with React or Vue from the user side.
  • Browser DOM API: createElement, appendChild, setAttribute, event listeners.

4. Theory & research

Required reading

  • Rodrigo Pombo, "Build your own React" (pomb.us/build-your-own-react/) — the canonical tutorial. Builds Didact (a React clone) in clear steps. ⭐ recommended primary.
  • Dan Abramov, "Overreacted" blogoverreacted.io. Many posts on React internals by a core contributor.
  • Dan Abramov, "A (Mostly) Complete Guide to React Rendering Behavior" — for understanding what React actually does.
  • React source codepackages/react-reconciler/. Substantial; skim with purpose.
  • Lin Clark, "A Cartoon Intro to React Fiber" (RSJS 2017) — video. Clarifies fiber in 20 minutes.

For component patterns

  • Sebastian Markbåge, "Algebraic Effects, Fibers, Coroutines" — historical motivation for hooks and fibers.

5. Curated tutorial list (from BYO-X)

  • JavaScript: WTF is JSX (Let's Build a JSX Renderer)
  • JavaScript: A DIY guide to build your own React
  • JavaScript: Building React From Scratch [video]
  • JavaScript: Gooact: React in 160 lines of JavaScriptgithub.com/sweetpalma/gooact
  • JavaScript: Learn how React Reconciler package works by building your own lightweight React DOM
  • JavaScript: Build Yourself a Redux
  • JavaScript: Let's Write Redux!
  • JavaScript: Redux: Implementing Store from Scratch [video]
  • JavaScript: Build Your own Simplified AngularJS in 200 Lines of JavaScript
  • JavaScript: Make Your Own AngularJS
  • JavaScript: How to write your own Virtual DOM
  • JavaScript: Building a frontend framework, from scratch, with components (templating, state, VDOM)
  • JavaScript: Build your own ReactRodrigo Pombo, pomb.usrecommended primary
  • JavaScript: Building a Custom React Renderer [video]

Rodrigo Pombo, "Build your own React".

15 steps, ~150 lines total. Builds Didact:

  1. createElement (JSX-replacement).
  2. render (initial mount).
  3. Concurrent mode (work loop using requestIdleCallback).
  4. Fibers (the data structure).
  5. Render and commit phases.
  6. Reconciliation.
  7. Function components.
  8. Hooks (useState).

This 15-step path is short enough to finish in 1–2 days and covers everything that matters conceptually.

For more depth on Redux: "Let's Write Redux" is similarly short and excellent.

For Angular: Tero Parviainen, "Make Your Own AngularJS" — a full book.


7. Implementation milestones

Milestone 1: createElement and JSX

JSX is syntax sugar for createElement calls. Babel transpiles it.

// JSX:
const el = <div id="foo">Hello, world!</div>

// Transpiles to:
const el = createElement("div", { id: "foo" }, "Hello, world!")
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(c => typeof c === "object" ? c : createTextElement(c)),
},
};
}
function createTextElement(text) {
return { type: "TEXT_ELEMENT", props: { nodeValue: text, children: [] } };
}

Evidence: createElement('div', null, 'hi') produces the expected JS object.

Milestone 2: render (initial mount)

Walk the VDOM tree and create real DOM nodes.

function render(element, container) {
const dom = element.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(element.type);
Object.keys(element.props)
.filter(k => k !== "children")
.forEach(k => dom[k] = element.props[k]);
element.props.children.forEach(child => render(child, dom));
container.appendChild(dom);
}

Evidence: render(<App />, document.getElementById('root')) puts the UI on screen.

Milestone 3: Concurrent rendering with fibers

Break the render into units of work. Each unit handles one fiber. After each unit, check if the browser has more important work (paint, input).

let nextUnitOfWork = null;

function workLoop(deadline) {
let shouldYield = false;
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 1;
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);

A fiber is a unit of work — one element + a pointer to its child, sibling, and parent fibers.

Evidence: Rendering 10,000 components doesn't block the main thread. Animations stay smooth.

Milestone 4: Render and commit phases

Pombo's key insight: separate render (mutate fibers in-memory) from commit (apply to real DOM in one go). Otherwise the user sees half-rendered UI.

function commitRoot() {
commitWork(wipRoot.child);
wipRoot = null;
}
function commitWork(fiber) {
if (!fiber) return;
fiber.parent.dom.appendChild(fiber.dom);
commitWork(fiber.child);
commitWork(fiber.sibling);
}

Milestone 5: Reconciliation

When state changes, compare new VDOM vs old VDOM. For each fiber:

  • Same type → update props on existing DOM node.
  • Different type → remove old, create new.
  • New element → create.
  • Missing element → delete.
function reconcileChildren(wipFiber, elements) {
// ... walk old and new lists in parallel, tag fibers as UPDATE / PLACEMENT / DELETION
}

The simple reconciliation algorithm: same position + same type = same component. Keys allow you to override this for lists.

Evidence: Editing a text input doesn't reset its focus — reconciliation correctly updates the existing DOM node.

Milestone 6: Function components

Function components produce VDOM from props:

function App({ name }) {
return <h1>Hello, {name}</h1>;
}

When the framework encounters a function-component fiber, it calls the function with the fiber's props to get its children.

Milestone 7: Hooks

useState stores state by call order. The framework maintains a per-fiber hook array; each useState call advances an index.

let wipFiber = null;
let hookIndex = null;

function useState(initial) {
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
const hook = { state: oldHook ? oldHook.state : initial, queue: [] };

const actions = oldHook ? oldHook.queue : [];
actions.forEach(action => { hook.state = action(hook.state); });

const setState = action => {
hook.queue.push(typeof action === "function" ? action : () => action);
wipRoot = { dom: currentRoot.dom, props: currentRoot.props, alternate: currentRoot };
nextUnitOfWork = wipRoot;
deletions = [];
};

wipFiber.hooks.push(hook);
hookIndex++;
return [hook.state, setState];
}

This is literally why hooks must be called in the same order every render. They're stored in an array indexed by call order.

Evidence: Counter component works:

function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

Milestone 8 (optional): useEffect

After commit, run side-effect functions. Skip if dependencies haven't changed.

Milestone 9 (optional): Context API

Pass values through the tree without prop drilling. Each Provider stores a value; useContext looks up the nearest provider.


8. Tests & evidence

TestHow
Initial mountTree of elements renders correctly
Re-render on state changeCounter increments
Reconciliation preserves identityInput doesn't lose focus on parent re-render
Conditional rendering{condition && <El/>} removes and re-adds correctly
List rendering{items.map(...)} works; keys allow reorder
Hooks rulesCalling useState in a different order throws
Performance1,000-item list re-renders without jank

The strongest evidence: a side-by-side React vs your framework comparison, running the same TodoMVC-style app.


9. Common pitfalls

  • Forgetting to handle text nodes specially. They're not elements.
  • Mutating fibers during render. Two-pass design (render then commit) avoids this.
  • Hooks called conditionally. Breaks the array index. React's eslint-plugin-react-hooks enforces this rule.
  • Event handlers re-added every render. Solution: track which props are event listeners; remove old, add new.
  • No batching. Multiple setState calls in the same handler should produce one re-render. React 18 batches by default.
  • Ignoring the deletion list. When elements are removed, their DOM nodes must be cleaned up.
  • Not using keys. Without keys, list reordering causes unnecessary DOM thrash.

10. Extensions

  • Suspense and data fetching — React 18 / 19 territory.
  • Server-side rendering (SSR). Render to HTML strings on the server.
  • Concurrent featuresuseTransition, useDeferredValue.
  • Custom renderers — react-three-fiber, ink (terminal), react-native.
  • Compiler-based optimization — Solid.js compiles JSX to fine-grained reactive updates with no VDOM.

The path from "tiny React" to "production framework" runs through real-world performance work. React itself has been refined for a decade.


11. Module integration

ModuleWhat the framework deepens
Sem 8 Module 1 — System designAPI surface design forces decisions you can articulate.
Sem 8 Module 2 — Microservices(Framework lives in the client; less relevant.)
Sem 7 Module 2 — Modular architectureRender and commit phases are textbook separation of concerns.
Interpreter tutorialJSX is a small language. Same parsing techniques.
Compiler tutorialSolid.js's compile-time approach is genuinely compiler work.

12. Portfolio framing

What to publish:

  • Source organized as src/{element, render, reconcile, hooks}.js.
  • An example app (TodoMVC, Hacker News reader, a small game).
  • A performance benchmark: render 1,000-item list, measure FPS and frame budget.
  • A README with:
    • Architecture diagram (fiber, work loop, render/commit).
    • Supported features.
    • Limitations.

Reviewer entry points:

  • src/render.js — the work loop.
  • src/reconcile.js — the diff.
  • src/hooks.js — the hooks array.
  • demo/todomvc/ — the working app.
  • README must include: feature list, performance numbers, comparison to React on the same demo.

A working React-like framework is an exceptional portfolio piece for front-end engineers. Everyone uses React; almost no one understands its internals.


Source

This tutorial draws from the BYO-X catalog "Front-end Framework / Library" section. Rodrigo Pombo's "Build your own React" is the canonical primary source.