Build your own reactivity system
Jan 04 2025
James Dawson
TLDR: You can explore the complete project on StackBlitz.
Modern-day programming is all about speed. You install a framework, a UI library, let AI generate your code and have something that is pretty impressive off the bat. These tools are incredible, but many experienced developers worry that newer programmers might stop questioning how all this abstraction works under the hood.. I would argue with AI, there has never been a better time to get a deeper and more specific knowledge on how the frameworks and plugins that we use everyday are built and understood. You have in your pocket a reasonably competent colleague who has moderately deep domain knowledge in many popular niches.
So let's put it to use! Today we are going to look at the different reactivity systems for frontend frameworks. Their strengths and trade offs, which frameworks have chose which and what is really happening under all this abstraction.
“Hello, my name is James, and I like to ramble on about code, design, and building products. If that is interesting to you, you can subscribe here to receive these blogs directly to your mailbox.”
What is a reactivity system?
Reactivity is a common programming paradigm for mutating or "reacting" to changes between two pieces of separate data. A reactive system automatically updates dependent values when data changes, like how a spreadsheet formula adjusts when you modify a cell.
A | B | C |
---|---|---|
1 | ||
2 | ||
=SUM(A1, A2) |
This simple formula will create an output like the one below.
A | B | C |
---|---|---|
1 | ||
2 | ||
3 |
In a spreadsheet a basic SUM formula is considered to be reactive. Since if we mutate A2, which is one of the formula's dependencies, the program will update A3 automatically.
A | B | C |
---|---|---|
1 | ||
4 | ||
5 |
This is the nuts and bolts of a reactive system. Now how can we represent the same thing in JavaScript?
A basic reactivity system
Let's try and create and simple reactivity system that will emulate the spreadsheet above. We will be using only typescript for this project. As always you can see the complete projecthere.
The reactive property
We will create a basic typescript composable called ref.ts
. This will be the building block for our reactive system, there are three pieces of functionality that we need.
get
the current value of theref
subscribe
to theref
and execute some code when theref
's active value is updatedset
a new value of theref
, execute anysubscribers
who are listening for changes
type Subscriber<T> = (newValue: T) => void;
export function ref(value: string | number) {
if (value === undefined || value === null) {
throw new Error('Error: Ref must be defined');
}
let activeValue = value;
const subscribers: Subscriber<typeof value>[] = [];
// Get the active value
function get() {
return activeValue;
}
// Sets a new value and calls each subscribe that has been appeneded
function set(newValue: typeof value) {
if (activeValue !== newValue) {
activeValue = newValue;
subscribers.forEach((subscriber) => subscriber(activeValue));
}
}
// subscribes to this reactive property, when set is called all subscribers will execute as well!
function subscribe(subscriber: Subscriber<typeof value>) {
subscribers.push(subscriber);
return () => {
const index = subscribers.indexOf(subscriber);
if (index > -1) {
subscribers.splice(index, 1);
}
};
}
return { get, set, subscribe };
}
Above we have the final output, I always think it is good to have a quick scan over the whole code and then break everything down individually.
get()
This function is very simple and just returns the current active value
subscribe()
The subscribe
function takes a callback function as its parameter and push this function to the subscribers array, that is it! The code will now execute when to the active value is updated. We also have some unsubscribe logic baked into the subscribe function.
return () => {
const index = subscribers.indexOf(subscriber);
if (index > -1) {
subscribers.splice(index, 1);
}
};
You would be able to unsubscribe from a ref
like this.
const unsubscribe = subscribe((newValue) => { console.log('New Value:', newValue); });
// This will unsubscribe from listening to updates
unsubscribe();
set()
Where all the magic happens! When we set a new value, we update the activeValue
and loop through all the functions that are stored in the subscribers array and execute each one.
That is pretty much all that is going on!
Let's see it in action
In the stackblitz I made a basic app for you to play around and see how the reactivity works. Let's break it down.
const app = document.querySelector<HTMLDivElement>('#app')!;
// create basic elements
const firstElement = document.createElement('div');
const secondElement = document.createElement('div');
const inputElement = document.createElement('input');
inputElement.type = 'text';
inputElement.placeholder = 'Update title...';
// append to DOM
app.appendChild(firstElement);
app.appendChild(secondElement);
app.appendChild(inputElement);
We are grabbing out app
element and appending a two divs and a single input to the DOM.
// Create reactive property
const title = ref('Subscribe to the blog');
firstElement.innerHTML = `First element: ${title.get()}`;
secondElement.innerHTML = `Second Element ${title.get()}`;
// Create two subscribers to the reactive value
title.subscribe((newTitle) => {
firstElement.innerHTML = `First element: ${newTitle}`;
});
title.subscribe((newTitle) => {
secondElement.innerHTML = `Second element: ${newTitle}`;
});
We then create a reactive string variable using our ref
composable. Then we set the innerHTML
to the first and second div to use the ref
's get()
function. Afterwards, we subscribe to the reactive title
and on a change we update the firstElement's innerHTML
to the new value and we do the same for the second element.
Finally we have add an event listener to the input to set the new value for the title
when the input is updated.
inputElement.addEventListener('input', (event) => {
let value = (event.target as HTMLInputElement).value;
value = value === '' ? 'Subscribe the the blog' : value;
title.set(value);
});
Conclusion
By building this simple reactive system, you've taken a first step into understanding how popular frameworks handle data binding under the hood. Different frameworks optimize this concept in unique ways, handling edge cases like batching updates and performance optimization. We'll dive into those advanced patterns next week, so stay tuned!
Become a better builder
Join our newsletter for frontend tip and tricks.
New blog posts every Sunday.