0

I am seeing a weird behavior with a simple custom React Hook I am trying to come up with. Any ideas on why it's not working as intended would be greatly appreciated.

So I have a custom hook called useProxyState.

import { useState } from 'react';
export function useProxyState(initValue: any) {
    if (typeof initValue !== 'object') initValue = { value: initValue };
    const [state, setState] = useState(initValue);
    const handlers: {} = {
        get: (target: any, prop: string, reciever: any) => {
            return state[prop];
        },
        set: (target: any, prop: string, value: any, reciever: any) => {
            Reflect.set(target, prop, value, reciever);
            setState((prevState: any) => (prevState = target));
            return true;
        },
    };
    return new Proxy(initValue, handlers);
}

Basically, I am returning a proxy that returns the state on the getter and sets the state on a setter.

So the odd behavior comes in when I am trying to use this custom hook. Below is my simple App component.

import './App.css';
import { useProxyState } from './Hooks/useProxy/useProxyState';
import { useState } from 'react';

export default function App() {
    const [input, setInput] = useState('');
    const count = useProxyState(0);

    const clickHandler = () => {
        count.value++;
    };

    return (
        <div className='App'>
            <div className='card'>
                <button onClick={clickHandler}>count is {count.value}</button>
            </div>
            <input value={input} onChange={(e) => setInput(e.target.value)} type='text' />
            <div>{input}</div>
        </div>
    );
}

I have it set up so that when I click the button, my proxy state's value should increase by 1. The issue is, when first loading the app and clicking the button, nothing happens (state remains at 0).

But as soon as I change the value in the input box, the state in the button updates and then when clicking, the state reacts as intended.

Does anyone know what may be causing this? Meaning why do I need to update the input's state before the button's state will become reactive?

I have tried multiple "hacks" to try and get this to work, but I think I am missing something internal to React at this point.


Edit: Here is the working code for anyone that is interested. Thanks @CertainPerformance

import { useState } from 'react';

export function useProxyState(obj: any) {
    if (typeof obj !== 'object') obj = { value: obj };
    const [state, setState] = useState(obj);
    const handlers: {} = {
        get: (target: any, prop: string, reciever: any) => {
            return state[prop];
        },
        set: (target: any, prop: string, value: any, reciever: any) => {
            Reflect.set(target, prop, value, reciever);
            setState(target);
            return true;
        },
    };
    let proxy: any = new Proxy({}, handlers);
    return proxy;
}
import './App.css';
import { useProxyState } from './Hooks/useProxy/useProxyState';
import { useState } from 'react';

export default function App() {
    const [input, setInput] = useState('');
    let count: any = useProxyState(0); //If initial State is a primitive, use <proxy>.value to access

    const clickHandler = () => {
        count.value++;
        console.log(count);
    };

    return (
        <div className='App'>
            <div className='card'>
                <button onClick={clickHandler}>count is {count.value}</button>
            </div>
            <input value={input} onChange={(e) => setInput(e.target.value)} type='text' />
            <div>{input}</div>
        </div>
    );
}
  • You are directly mutating the state object instead of setting the new state to an updated copy, and as a result, re-renderings don't work as expected. Never mutate state in React – CertainPerformance Jan 16 '23 at 06:18
  • @CertainPerformance Do you mind explaining a little more, I am not directly mutating the state object. I am mutating the proxy which then calls setState inside the custom hook. – Hunter Woodall Jan 16 '23 at 06:23
  • `Reflect.set(target, prop, value` is like `target[prop] = value` - and `target` is `initValue`, which is the state inside your hook, which you're mutating. – CertainPerformance Jan 16 '23 at 06:25
  • @CertainPerformance I understand what you are saying, but is target not referring to the proxy in this situation? Also, do you have any suggestions on how to fix this issue. And lastly, do you know why the custom hook is working correctly after editing the input box? – Hunter Woodall Jan 16 '23 at 06:30
  • No, `target` is the *target* of the proxy - which is first argument passed when constructing the proxy, the `initValue`. It sounds like editing the input box results a re-render, so the custom hook gets called again and the changed state then gets reflected in the render. – CertainPerformance Jan 16 '23 at 06:31
  • @CertainPerformance So what I find odd is that after editing the input box, the button works as expected after each click (meaning I no longer need to update the input to see the counter be updated on the page) – Hunter Woodall Jan 16 '23 at 06:35

0 Answers0