DEV Community

Cover image for Messing around with Proxy objects in Javascript!
Tarush Nagpal
Tarush Nagpal

Posted on

Messing around with Proxy objects in Javascript!

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);
...
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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,

  1. For a backend developer -> It's a middleware
  2. For a OOP developer -> It's an overridden getter/setter/(Plus a few more)
  3. 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;
  },
});
Enter fullscreen mode Exit fullscreen mode

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'));

Enter fullscreen mode Exit fullscreen mode

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);
};
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
akigugale profile image
Akshay Gugale

Noice!