DEV Community

Cover image for Proxies in React: The Sneaky State Spy You Didn’t Know You Needed
Ravi Dhiman
Ravi Dhiman

Posted on

Proxies in React: The Sneaky State Spy You Didn’t Know You Needed

Have you heard of Javascript Proxy? I manage to replace useReducer by using it inside React component.

In this post, I’ll show you how to leverage proxies to seamlessly detect changes in nested state, keep your components updated, and say goodbye to manual deep-cloning and other headaches.

What’s the Big Deal About Proxies?

JavaScript’s Proxy is like a super-spy that can intercept and customize operations performed on an object, such as getting, setting, or deleting properties. This makes it perfect for listening to state changes, even in nested objects, without the need for deep comparison or unnecessary re-renders.

Here’s what we’re aiming for:

  • Reactive state that updates automatically when any nested property changes.
  • No more manual cloning of deeply nested objects to trigger React updates.
  • Handling arrays and nested structures with minimal effort.

Getting Started: Setting Up the Proxy

Before we dive into integrating with React, let’s break down how to set up a proxy that handles nested objects. The idea is simple: wrap your initial state in a proxy that can spy on any changes and trigger an update when needed.

Here’s a basic example:

function createNestedProxy(state, onChange) {
  if (typeof state !== 'object' || state === null) {
    return state; // Return primitive values as-is
  }

  const handler = {
    get(target, property) {
      const value = target[property];
      if (typeof value === 'object' && value !== null) {
        return createNestedProxy(value, onChange); // Recursively proxy nested objects
      }
      return value;
    },
    set(target, property, value) {
      if (target[property] === value) return true; // No change, no update

      if (typeof value === 'object' && value !== null) {
        value = createNestedProxy(value, onChange); // Proxy nested objects
      }
      target[property] = value;
      onChange(); // Trigger the change callback
      return true;
    }
  };

  return new Proxy(state, handler);
}

Enter fullscreen mode Exit fullscreen mode

Integrating the Proxy with React

Now comes the fun part—integrating this proxy setup into a React hook! We’ll create a useProxyState hook that wraps our initial state and ensures any changes automatically trigger React re-renders.

import { useState, useEffect } from 'react';

export function useProxyState(initialState) {
  const [state, setState] = useState(initialState);

  useEffect(() => {
    // Create a proxy with the onChange callback to trigger state updates
    const proxiedState = createProxyState(state, st => {
      // Trigger a React state update
      console.log('state updated');
      setState({ ...proxiedState });
    });

    // Set the state to the proxied state on the first render
    setState(proxiedState);
  }, []);

  return state;
}
Enter fullscreen mode Exit fullscreen mode

Let's use it into a react component

import { useProxyState } from './proxy';

function App() {
  const state = useProxyState({
    name: "Alice",
    details: {
      age: 25,
      hobbies: ["reading", "coding"]
    }
  });

  const updateName = () => {
    state.name = "Bob";
  };

  const addHobby = () => {
    state.details.hobbies.push("gaming");
  };

  return (
    <div>
      <h1>Name: {state.name}</h1>
      <h2>Age: {state.details.age}</h2>
      <h3>Hobbies:</h3>
      <ul>
        {state.details.hobbies.map((hobby, index) => (
          <li key={index}>{hobby}</li>
        ))}
      </ul>
      <button onClick={updateName}>Update Name</button>
      <button onClick={addHobby}>Add Hobby</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Pitfalls and Things to Watch Out For

While proxies are powerful, there are some caveats:

  • Be mindful of performance in very large or deeply nested objects.
  • JavaScript’s Proxy doesn’t work in environments that don’t support it (like older browsers).

Top comments (0)