Building React-like Class Components from Scratch (ES5 Prototypes & Mounting)
Building React-like components in ES5 involves creating a base `Component` constructor function with `setState` and `render` on `Component.prototype`, implementing prototype inheritance via `Object.create`, and wiring a micro-reconciler to mount and re-render the DOM tree.
Why it matters in interviews
Visual & Interactive Explanation
1. Instantiate
new MyComponent(props)
2. Initial Render
vdom = instance.render()
3. DOM Mount
domNode = mount(vdom, container)
4. setState() Called
Queue update -> microtask flush
5. Re-render
newVdom = instance.render() -> patch DOM
Code Examples & Implementation
// --- 1. BASE COMPONENT CONSTRUCTOR ---
function Component(props) {
this.props = props || {};
this.state = {};
this._dom = null;
this._pendingState = [];
this._isBatching = false;
}
// Attach setState to prototype
Component.prototype.setState = function(partialState) {
this._pendingState.push(partialState);
if (!this._isBatching) {
this._isBatching = true;
queueMicrotask(() => {
this._flushState();
});
}
};
Component.prototype._flushState = function() {
// Merge all queued state updates
while (this._pendingState.length > 0) {
const next = this._pendingState.shift();
const resolved = typeof next === "function" ? next(this.state, this.props) : next;
this.state = Object.assign({}, this.state, resolved);
}
this._isBatching = false;
this._update();
};
Component.prototype._update = function() {
if (!this._dom || !this._dom.parentNode) return;
const newVNode = this.render();
const newDom = mount(newVNode);
this._dom.parentNode.replaceChild(newDom, this._dom);
this._dom = newDom;
};
// --- 2. VDOM & MOUNTING PIPELINE ---
function createElement(type, props, ...children) {
return {
type: type,
props: Object.assign({}, props, {
children: children.flat().filter((c) => c !== null && c !== undefined && c !== false),
}),
};
}
function mount(vnode, container) {
// Text node
if (typeof vnode === "string" || typeof vnode === "number") {
const textNode = document.createTextNode(String(vnode));
if (container) container.appendChild(textNode);
return textNode;
}
// Class Component
if (typeof vnode.type === "function") {
const instance = new vnode.type(vnode.props);
const renderedVNode = instance.render();
const dom = mount(renderedVNode);
instance._dom = dom;
if (instance.componentDidMount) instance.componentDidMount();
if (container) container.appendChild(dom);
return dom;
}
// HTML Element (div, button, h1)
const dom = document.createElement(vnode.type);
// Attach props & event listeners
if (vnode.props) {
Object.keys(vnode.props).forEach((key) => {
if (key === "children") return;
if (key.startsWith("on")) {
const eventName = key.slice(2).toLowerCase();
dom.addEventListener(eventName, vnode.props[key]);
} else {
dom.setAttribute(key, vnode.props[key]);
}
});
if (vnode.props.children) {
vnode.props.children.forEach((child) => mount(child, dom));
}
}
if (container) container.appendChild(dom);
return dom;
}// --- 3. CUSTOM USER COMPONENT IN PURE ES5 ---
function CounterComponent(props) {
// Call super constructor
Component.call(this, props);
this.state = { count: 0 };
}
// Inherit from Component.prototype
CounterComponent.prototype = Object.create(Component.prototype);
CounterComponent.prototype.constructor = CounterComponent;
// Implement render method
CounterComponent.prototype.render = function() {
return createElement(
"div",
{ class: "counter-box" },
createElement("h1", null, "Count: " + this.state.count),
createElement(
"button",
{
onClick: () => {
this.setState({ count: this.state.count + 1 });
},
},
"Increment"
)
);
};
// Mount into DOM
// mount(createElement(CounterComponent, null), document.getElementById("root"));Interview-Ready Answers
Create a `Component` constructor function, attach `setState` to its prototype to merge partial state and schedule a re-render, inherit using `Object.create(Component.prototype)`, and write a `mount` function to convert VNodes to real DOM nodes.
Follow-up Questions & Deep Dives
🎯 Code Output — What Does This Print?
function Parent() {}
function Child() {}
Child.prototype = Object.create(Parent.prototype);
console.log(Child.prototype.constructor === Parent);
console.log(new Child() instanceof Parent);Common Mistakes & Anti-Patterns
- Mutating `this.state` directly instead of merging in `setState`
- Forgetting to flat/filter children arrays when creating VNodes
- Not binding event listener callbacks, causing `this` to point to the DOM element instead of the component instance
Implementation Evolution: Anti-Pattern to Production
❌ Anti-Pattern: Direct Prototype Assignment (`Child.prototype = Parent.prototype`)
// ❌ Fatal Anti-Pattern: Mutating the base prototype directly
function MyComponent() {}
// Broken: This points to the EXACT SAME object in memory!
MyComponent.prototype = Component.prototype;
// Adding methods to MyComponent now pollutes ALL components in the entire app!
MyComponent.prototype.customMethod = function() {};✅ Correct Pattern: Clean Prototype Inheritance with `Object.create`
// ✅ Correct: Creates a fresh prototype object linked to Component.prototype
function MyComponent(props) {
Component.call(this, props);
}
MyComponent.prototype = Object.create(Component.prototype);
MyComponent.prototype.constructor = MyComponent; // Restore constructor pointer!🚀 Production-Grade (Hardened): ES5 Component Helper with Lifecycle & Shallow State Merge
// 🚀 Production: Factory helper similar to React.createClass (ES5 Standard)
function createClass(spec) {
function Constructor(props) {
Component.call(this, props);
if (spec.getInitialState) {
this.state = spec.getInitialState.call(this);
}
}
Constructor.prototype = Object.create(Component.prototype);
Constructor.prototype.constructor = Constructor;
// Copy spec methods to prototype and bind auto-methods
Object.keys(spec).forEach(function(key) {
if (key !== "getInitialState") {
Constructor.prototype[key] = spec[key];
}
});
return Constructor;
}⚠️ Trick Questions & Interviewer Traps
Q1:Why must you reset `Child.prototype.constructor = Child` after `Object.create`?
Q2:Why do we call `Component.call(this, props)` inside the subclass constructor?
📋 Rapid Revision Cheat Sheet
- Base Constructor: `function Component(props) { this.props = props; this.state = {}; }`.
- Inheritance: `Sub.prototype = Object.create(Component.prototype); Sub.prototype.constructor = Sub;`.
- Super Call: `Component.call(this, props)` initializes instance fields.
- setState: Merges partial state and schedules microtask `_update()` flush.
- Mount: Inspects `typeof vnode.type === 'function'` to instantiate class and call `.render()`.
Real-World Architectural Scenario
An interviewer asks you to build React without using ES6 classes or JSX. How do you implement `setState` and component rendering in 30 lines of code?
Rate Your Readiness
Rate how comfortably you can explain this in an interview to update your global readiness gauge: