useProxy: A Comprehensive Guide
Learn how to use the useProxy hook - a powerful React utility for creating Proxy objects that enable interception and customization of object operations, perfect for implementing reactive objects and data transformations.
By usehooks.io
Introduction
The useProxy hook is a powerful React utility that creates and manages JavaScript Proxy objects, allowing you to intercept and customize operations performed on objects. This hook provides a clean way to implement reactive objects, validation, logging, or data transformation in your React applications.
What is a JavaScript Proxy?
Before diving into the hook, let's understand what a JavaScript Proxy is. The Proxy object enables you to create a wrapper for another object, which can intercept and redefine fundamental operations for that object (like property lookup, assignment, enumeration, function invocation, etc.).
Installation
The useProxy hook is part of the usehooks.io collection. You can use it in your project by installing the package:
1npx usehooks-cli/latest add use-proxy
2
Basic usage
Here's how to import and use the useProxy hook in your React components:
1import { useProxy } from "@hooks/use-proxy";
2
3function MyComponent() {
4 const { proxy } = useProxy(
5 { name: "John", age: 30 }, // Initial target object
6 {
7 // Handler with traps
8 get(target, prop) {
9 console.log(`Accessing property: ${String(prop)}`);
10 return target[prop];
11 },
12 }
13 );
14
15 return (
16 <div>
17 <p>Name: {proxy.name}</p> {/* Will log "Accessing property: name" */}
18 <p>Age: {proxy.age}</p> {/* Will log "Accessing property: age" */}
19 </div>
20 );
21}
22
API Reference
Parameters
The useProxy hook accepts two parameters:
- initialTarget (required): The initial object to be proxied. This must be an object.
- handler (required): An object that defines which operations will be intercepted and how to redefine those operations.
Return Value
The hook returns an object with the following properties:
- proxy : The proxy object that intercepts operations on the target.
- target : The original target object (useful for direct access).
- updateTarget : A function to update the target object. Accepts either a new target object or a function that receives the previous target and returns a new one.
- revoke : A function to revoke the proxy, making it unusable.
- isRevoked : A boolean indicating whether the proxy has been revoked.
Handler Methods (Traps)
The handler object can include any of these methods to intercept different operations:
- get : Intercepts property access
- set : Intercepts property assignment
- has : Intercepts the in operator
- deleteProperty : Intercepts the delete operator
- ownKeys : Intercepts Object.keys , Object.getOwnPropertyNames , etc.
- getOwnPropertyDescriptor : Intercepts Object.getOwnPropertyDescriptor
- defineProperty : Intercepts Object.defineProperty
- preventExtensions : Intercepts Object.preventExtensions
- getPrototypeOf : Intercepts Object.getPrototypeOf
- isExtensible : Intercepts Object.isExtensible
- setPrototypeOf : Intercepts Object.setPrototypeOf
- apply : Intercepts function calls
- construct : Intercepts the new operator
Examples
1. Property Access Logging
This example demonstrates how to create a proxy that logs every time a property is accessed on an object. This is useful for debugging and understanding how your application interacts with objects:
1const { proxy } = useProxy(
2 { name: "John", age: 30 },
3 {
4 get(target, prop) {
5 console.log(`Accessing property: ${String(prop)}`);
6 return target[prop];
7 },
8 }
9);
10
11// Usage: proxy.name will log "Accessing property: name"
12
2. Property Validation
Validate property assignments by intercepting the set trap. This example ensures that the 'age' property can only be set to positive numbers:
1const { proxy } = useProxy(
2 { age: 0 },
3 {
4 set(target, prop, value) {
5 if (prop === "age" && (typeof value !== "number" || value < 0)) {
6 throw new Error("Age must be a positive number");
7 }
8 target[prop] = value;
9 return true;
10 },
11 }
12);
13
14// proxy.age = -5; // Throws error
15
3. Default Values
Provide default values for undefined properties. This is useful when you want to ensure that accessing any non-existent property returns a fallback value instead of undefined:
1const { proxy } = useProxy(
2 {},
3 {
4 get(target, prop) {
5 return prop in target ? target[prop] : "default";
6 },
7 }
8);
9
10// proxy.anyProperty returns 'default' if not set
11
4. Revocable Proxy
Create a proxy that can be revoked (useful for temporarily granting and then revoking access to sensitive data or functionality):
1const { proxy, revoke, isRevoked } = useProxy(
2 { data: "sensitive" },
3 {
4 get(target, prop) {
5 console.log("Access granted");
6 return target[prop];
7 },
8 }
9);
10
11// Later revoke access
12revoke();
13console.log(isRevoked); // true
14
5. Updating the Target Object
The updateTarget
function allows you to modify the target object while maintaining proxy functionality. This example demonstrates a simple counter implementation that logs each time the count is accessed.
1function Counter() {
2 const { proxy, updateTarget } = useProxy(
3 { count: 0 },
4 {
5 get(target, prop) {
6 console.log(`Reading ${String(prop)}: ${target[prop]}`);
7 return target[prop];
8 },
9 }
10 );
11
12 const increment = () => {
13 updateTarget((prev) => ({ ...prev, count: prev.count + 1 }));
14 };
15
16 return (
17 <div>
18 <p>Count: {proxy.count}</p>
19 <button onClick={increment}>Increment</button>
20 </div>
21 );
22}
23
6. Form Validation
This example demonstrates a comprehensive form validation implementation using the useProxy hook. The form includes username and email fields with real-time validation. The proxy intercepts field updates to clear related errors, while the validation logic checks for required fields and proper email format. The form state is managed through the proxy, with errors stored in a dedicated errors object. Let's break down each part in detail:
1function UserForm() {
2 const { proxy, updateTarget } = useProxy(
3 { username: "", email: "", errors: {} },
4 {
5 set(target, prop, value) {
6 // Clear errors when a field is updated
7 if (prop === "username" || prop === "email") {
8 target.errors = { ...target.errors };
9 delete target.errors[prop];
10 }
11
12 target[prop] = value;
13 return true;
14 },
15 }
16 );
17
18 const handleChange = (e) => {
19 const { name, value } = e.target;
20 updateTarget((prev) => ({ ...prev, [name]: value }));
21 };
22
23 const validate = () => {
24 const errors = {};
25
26 if (!proxy.username) errors.username = "Username is required";
27 if (!proxy.email) errors.email = "Email is required";
28 else if (!/\S+@\S+\.\S+/.test(proxy.email))
29 errors.email = "Email is invalid";
30
31 updateTarget((prev) => ({ ...prev, errors }));
32 return Object.keys(errors).length === 0;
33 };
34
35 const handleSubmit = (e) => {
36 e.preventDefault();
37 if (validate()) {
38 console.log("Form submitted:", {
39 username: proxy.username,
40 email: proxy.email,
41 });
42 }
43 };
44
45 return (
46 <form onSubmit={handleSubmit}>
47 <div>
48 <label>Username:</label>
49 <input name="username" value={proxy.username} onChange={handleChange} />
50 {proxy.errors.username && <p>{proxy.errors.username}</p>}
51 </div>
52
53 <div>
54 <label>Email:</label>
55 <input name="email" value={proxy.email} onChange={handleChange} />
56 {proxy.errors.email && <p>{proxy.errors.email}</p>}
57 </div>
58
59 <button type="submit">Submit</button>
60 </form>
61 );
62}
63
Important Notes
- The proxy is recreated when the target or handler changes.
- Once revoked, the proxy becomes unusable and operations will throw TypeError.
- The hook uses Proxy.revocable() to allow controlled revocation.
- All handler methods are optional - omitted traps forward to the target.
- This hook is perfect for implementing reactive objects, validation, or debugging.
Performance Considerations
- Proxies add a small overhead to property access and other operations.
- For performance-critical code, consider using direct access to the target object.
- Avoid creating new handler objects on each render to prevent unnecessary proxy recreation.
Conclusion
The useProxy hook provides a powerful way to intercept and customize object operations in React applications. It's particularly useful for implementing validation, logging, reactive data, and other advanced patterns. By leveraging JavaScript's Proxy API with React's state management, you can create more robust and maintainable applications.
Happy coding!