Back to JavaScript
JavaScript
easy
mid

How would you build React style class components from scratch in ES5?

Build a minimal class component model in plain ES5: a Component constructor with setState that re-renders, a tiny VDOM createElement/render pipeline, prototypal inheritance for user components. Demonstrates understanding of prototypes, state-update batching, and the render/diff/commit loop.

8 min read·~30 min to think through

This is a 'build mini-React' interview prompt. Demonstrates understanding of prototypes, state, and reconciliation.

ES5 prototypal Component

js
function Component(props) {
  this.props = props || {};
  this.state = {};
}

Component.prototype.setState = function (partial) {
  this.state = Object.assign({}, this.state, partial);
  rerender(this);
};

Component.prototype.render = function () {
  throw new Error('Subclass must implement render()');
};

Subclassing in ES5

js
function Counter(props) {
  Component.call(this, props);
  this.state = { count: 0 };
  this.inc = this.inc.bind(this);
}
Counter.prototype = Object.create(Component.prototype);
Counter.prototype.constructor = Counter;

Counter.prototype.inc = function () {
  this.setState({ count: this.state.count + 1 });
};

Counter.prototype.render = function () {
  return createElement('button', { onClick: this.inc }, String(this.state.count));
};

Tiny createElement

js
function createElement(type, props /*, ...children */) {
  var children = Array.prototype.slice.call(arguments, 2);
  return {
    type: type,
    props: Object.assign({}, props, { children: children }),
  };
}

Render to DOM

js
function renderElement(vnode) {
  if (typeof vnode === 'string' || typeof vnode === 'number') {
    return document.createTextNode(String(vnode));
  }
  if (typeof vnode.type === 'function') {
    var instance = new vnode.type(vnode.props);
    instance._vnode = vnode;
    var rendered = instance.render();
    var dom = renderElement(rendered);
    instance._dom = dom;
    return dom;
  }
  var el = document.createElement(vnode.type);
  Object.keys(vnode.props || {}).forEach(function (key) {
    if (key === 'children') return;
    if (key.indexOf('on') === 0) {
      el.addEventListener(key.slice(2).toLowerCase(), vnode.props[key]);
    } else {
      el.setAttribute(key, vnode.props[key]);
    }
  });
  (vnode.props.children || []).forEach(function (child) {
    el.appendChild(renderElement(child));
  });
  return el;
}

Re-render on setState

js
function rerender(instance) {
  var nextVDom = instance.render();
  var newDom = renderElement(nextVDom);
  if (instance._dom && instance._dom.parentNode) {
    instance._dom.parentNode.replaceChild(newDom, instance._dom);
  }
  instance._dom = newDom;
}

This is naive — replaces the entire subtree on each setState. Real React diffs.

Mounting

js
function mount(rootVDom, container) {
  container.appendChild(renderElement(rootVDom));
}

mount(createElement(Counter, {}), document.getElementById('app'));

What's missing

  • Diffing / reconciliation: compare old vs new vnode and patch.
  • Keys: stable identity across renders.
  • Batched setState: multiple setStates in one tick produce one render.
  • Lifecycle methods: componentDidMount, etc.
  • Hooks: function components, useState.
  • Synthetic events: React's event delegation system.
  • Fragments / context / portals.

What this demonstrates

  • ES5 prototypes (Object.create, prototype chains).
  • The VDOM as a plain-object tree.
  • State + re-render loop.
  • React's value comes from the diff algorithm, not the API.

How to lead

I'll build the Component class with setState, a tiny createElement, and a naive renderer that re-renders the subtree on each update. Diffing and hooks are deliberately out of scope; I'll point out what real React adds on top.

That framing signals seniority better than trying to reproduce all of React in 30 minutes.

Follow-up questions

  • How would you add diffing to avoid full subtree re-renders?
  • How would batching setState work?
  • How would you add hooks (useState) to this model?

Common mistakes

  • Forgetting to bind methods — 'this' is lost in callbacks.
  • Re-running constructors on every render — instances should persist.
  • Trying to write a full diff in 20 minutes — scope down deliberately.

Performance considerations

  • Naive replace-on-every-setState is O(n) DOM work per update. Real React's diff is O(n) but skips most of it via key matching and bailout. This is why React's value is the reconciler, not the API.

Edge cases

  • Replacing the DOM node breaks any descendant focus / scroll position.
  • Event listeners attached on each re-render leak — clean up old DOM.
  • Re-mounting on every setState is functionally correct but practically unusable.

Real-world examples

  • Educational walkthroughs like Rodrigo Pombo's 'Build your own React'. Preact at ~3 KB is essentially this idea, fully fleshed out.

Senior engineer discussion

Senior framing: this question rewards scoping. State explicitly what you'll build and what you won't. Show that you understand React's hard parts (diffing, scheduling, batching, hooks) even if you can't recreate them in the time you have.

Related questions