Proxies in React: The Sneaky State Spy You Didn’t Know You Needed
Ravi Dhiman
Posted on August 25, 2024
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);
}
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;
}
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>
);
}
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).
Posted on August 25, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.