
Learn How React Works by Building Your Own Framework
Over the last few years, I’ve met several fresh developers who jumped straight into React to build web applications. While that’s great, I believe the brilliance of React’s creators really shines when you understand how they transformed HTML+JS into a framework that forever changed front-end development.
React is beautiful
- Highly composable – Incredibly useful for large applications.
- Colocated concerns – You don’t need to jump between files to understand a single component. You don’t even need two languages. It’s just JS(X)!
Before React
I won’t take you too far back. Before React, there was plain HTML+JS. In fact, React ultimately boils down to just HTML and JS anyway. The browser lords didn’t have to change a thing to support React apps—because what gets rendered is still the same old HTML+JS. And that’s where the true brilliance of React lies.
Building Our Own Framework: ReReact
We’ll go through the following stages in this tutorial:
- A basic HTML + JS counter app as our foundation
- Gradually move everything into JS—no more HTML!
- Make our JS resemble React via composition (templating, JSX-like structure)
The Application
We’re going to build a simple counter app with 2 buttons and a text box that starts at zero and updates as we click the buttons.
Phase 1: Setting up HTML and JS
Here’s a basic HTML file that sets up 2 buttons, a span to display the counter, and a script tag to import JavaScript.
index.html
<html>
<body>
<div id="container">
<button onclick="decrement()">Dec</button>
<span id="counter">0</span>
<button onclick="increment()">Inc</button>
</div>
<script src="/index.js"></script>
</body>
</html>
index.js
function increment() {
// TODO: Implement increment
}
function decrement() {
// TODO: Implement decrement
}
Output:

Adding state
Let’s now implement the functions to update the counter.
Updated index.js
let counter = 0;
let counterSpan = document.getElementById("counter");
function updateUI() {
counterSpan.textContent = counter;
}
function increment() {
counter++;
updateUI();
}
function decrement() {
counter--;
updateUI();
}
Our counter app is now functional!
Updated Output:

So far, so good. Phase 1 complete. Let’s move on.
Phase 2: Move everything to JS
Even though a lot of logic is in JS, our app isn’t pure JS. We still rely on HTML for structure.
The test: if you can tell what the app is doing just by looking at index.html
, we haven’t gone far enough.
<html>
<body>
<!-- Looking at the elements inside the div, we can figure out that it's a counter app -->
<div id="container">
<button onclick="decrement()">
Dec
</button>
<span id="counter">
0
</span>
<button onclick="increment()">
Inc
</button>
</div>
<script src="/index.js"></script>
</body>
</html>
So let's move more logic to js. We are going to delete the elements from our html, and write code to create these elements purely in our js code.
Updated index.js
let counter = 0;
function updateUI() {
counterSpan.textContent = counter;
}
function increment() {
counter++;
updateUI();
}
function decrement() {
counter--;
updateUI();
}
let container = document.getElementById("container");
// Decrement button
let decrementBtn = document.createElement("button");
decrementBtn.onclick = decrement;
decrementBtn.textContent = "Dec";
// Counter span
let counterSpan = document.createElement("span");
counterSpan.id = "counter";
counterSpan.textContent = counter;
// Increment button
let incrementBtn = document.createElement("button");
incrementBtn.onclick = increment;
incrementBtn.textContent = "Inc";
function loadElements() {
container.replaceChildren();
container.appendChild(decrementBtn);
container.appendChild(counterSpan);
container.appendChild(incrementBtn);
}
loadElements();
Updated index.html
<html>
<body>
<div id="container"></div>
<script src="/index.js"></script>
</body>
</html>
And the app still works!

🎉 We’ve completed Phase 2. Our app is now fully JavaScript-driven!
Phase 3: React-style Composition
Our current code works, but doesn’t yet look like React. Let’s fix that with a more declarative, component-style structure.
Skeleton code
function CounterApp() {
return [
Button("Dec", { onClick: decrement }),
Span(counter),
Button("Inc", { onClick: increment }),
]
}
Component functions
function Button(children, { onClick }) {
const btn = document.createElement("button");
btn.textContent = children;
btn.onclick = onClick;
return btn;
}
function Span(children) {
const span = document.createElement("span");
span.textContent = children;
return span;
}
Render function
function render() {
container.replaceChildren();
let elements = CounterApp();
elements.forEach(element => container.appendChild(element));
}
Final index.js
let counter = 0;
function increment() {
counter++;
render();
}
function decrement() {
counter--;
render();
}
let container = document.getElementById("container");
function Button(children, { onClick }) {
const btn = document.createElement("button");
btn.textContent = children;
btn.onclick = onClick;
return btn;
}
function Span(children) {
const span = document.createElement("span");
span.textContent = children;
return span;
}
function CounterApp() {
return [
Button("Dec", { onClick: decrement }),
Span(counter),
Button("Inc", { onClick: increment }),
];
}
function render() {
container.replaceChildren();
let elements = CounterApp();
elements.forEach(el => container.appendChild(el));
}
render();
Extracting a Mini Library: ReReact
Let’s move our reusable functions to a new file called rereact.js
.
function Button(children, { onClick }) {
const btn = document.createElement("button");
btn.textContent = children;
btn.onclick = onClick;
return btn;
}
function Span(children) {
const span = document.createElement("span");
span.textContent = children;
return span;
}
let root;
let app;
function createRoot(container, component) {
root = container;
app = component;
render();
}
function render() {
root.replaceChildren();
let elements = app();
elements.forEach(el => root.appendChild(el));
}
Update the index.html
<html>
<body>
<div id="container"></div>
<!-- Import rereact -->
<script src="/rereact.js"></script>
<script src="/index.js"></script>
</body>
</html>
Cleaned up index.js
let counter = 0;
function increment() {
counter++;
render();
}
function decrement() {
counter--;
render();
}
function CounterApp() {
return [
Button("Dec", { onClick: decrement }),
Span(counter),
Button("Inc", { onClick: increment }),
];
}
let container = document.getElementById("container");
createRoot(container, CounterApp);
Bonus: Implementing useState
Let’s make useState()
so we don’t have to manage state variables or manually make render()
calls.
Add to rereact.js
let _state;
function useState(initialValue) {
_state = _state || initialValue;
function setState(newVal) {
if (_state !== newVal) {
_state = newVal;
render();
}
}
return [_state, setState];
}
Final index.js
function CounterApp() {
let [counter, setCounter] = useState(0);
return [
Button("Dec", { onClick: () => setCounter(counter - 1) }),
Span(counter),
Button("Inc", { onClick: () => setCounter(counter + 1) }),
];
}
let container = document.getElementById("container");
createRoot(container, CounterApp);
And voila, we have (Re)React! 🚀
Conclusion
The current implementation is obviously very limited:
- Only 2 built-in components:
Span
andButton
- Only supports arrays, no component nesting
useState()
is global – so only one state is allowed- No JSX compiler or diffing
But that’s not the point. This was a fun exercise to explore how React-like patterns emerge from vanilla HTML+JS. I hope this helped you appreciate the design behind frameworks like React.
Download the project files: rereact_demo.zip
Thanks for reading!