The Problem
Let's say you are working with a few isolated components that have no idea who their children are nor their parents (Because they could be any and many), now each of these components rely on certain information that updates continuously from a (say) library.
So now each component is expected to go on and say
...
library.addEventListener('someUpdate', doSomething);
...
But when your number of events and your number of components keep going up, you start approaching memory leak scenarios and just memory issues on browsers on lower tier machines/phones.
The solution?
What if we build a single store that listens to all of these events so you have only one listener for each type of property! BUT, how does each isolated component actually talk to the store, Another event listener? That was the problem we were trying to solve! 🤦♂️
Introducing,
The Proxy object.
I am not going to go into depth here but you can check out MDN.
Basically, a Proxy defined as,
const target = {
message1: "hello",
message2: "everyone",
};
const handler1 = {};
const proxy1 = new Proxy(target, handler1);
can handle
events happening to the target
and perform certain actions before or after actually doing what the event expected it to do.
In simpler terms,
- For a
backend
developer -> It's a middleware - For a
OOP
developer -> It's an overridden getter/setter/(Plus a few more) - For a
frontend
developer -> It's a Proxy!
Solving the problem
Now let's actually solve the problem.
Let's say we want two components to listen for resize events on a div. Now instead of each component adding their own individual listeners we are going to create a store.ts
that defines a proxy like this
/**
* Listener Store
*/
let store = {
windowHeight: 0,
windowWidth: 0,
};
let storeProxy = new Proxy(store, {
set(obj, prop, value) {
/**
* Can also put validation here
*/
obj[prop] = value;
return true;
},
});
Then creates a resize observer which updates the value of the proxy.
const resizeObserver = new ResizeObserver((entries) => {
const entry = entries[0];
const { width, height } = entry.contentRect;
storeProxy.windowHeight = height;
storeProxy.windowWidth = width;
});
resizeObserver.observe(document.getElementById('app'));
And we will also add a variable called callOnStoreUpdate
which will be an array of functions to be called whenever a store is updated, hence making the entirety of store.ts
/**
* Listener Store
*/
/**
* Holds an array of functions to call when any value
* in store updates
*/
let toCallOnUpdate = [];
let store = {
windowHeight: 0,
windowWidth: 0,
};
let storeProxy = new Proxy(store, {
set(obj, prop, value) {
/**
* Can also put validation here
*/
if (obj[prop] === value) return true;
obj[prop] = value;
toCallOnUpdate.forEach((fun) => fun(obj, prop, value));
return true;
},
});
const resizeObserver = new ResizeObserver((entries) => {
const entry = entries[0];
const { width, height } = entry.contentRect;
storeProxy.windowHeight = height;
storeProxy.windowWidth = width;
});
resizeObserver.observe(document.getElementById('app'));
export const callOnStoreUpdate = (fun: (obj, prop, value) => void) => {
toCallOnUpdate.push(fun);
};
I think now you might have understood where we were getting at.
We simply in how many ever components we want go and add
/**
* Sample component!
*/
import { callOnStoreUpdate } from '../listeners';
const onResize = (obj, prop, value) => {
switch (prop) {
case 'windowHeight':
console.log('This is component.ts reporting the window height!', value);
break;
case 'windowWidth':
console.log('This is component.ts reporting the window width!', value);
}
};
callOnStoreUpdate(onResize);
And boom 💥! We have managed to capture updates to a variable without the need to ever add more than one event listener.
I will be exploring a bit more into Proxy and just making sure I understood everything correctly, but I feel like this is an under-utilised web api which can really do crazy things!
This was a problem that we faced over at Dyte, check us out we're pretty cool.
Also a full working sample. Stackblitz
Top comments (1)
Noice!