How does react keep variables value in useCallback?

Viewed 113
const { useState, useCallback } = React;
let globalValue = 0;
const ComponentA = ({ propValue }) => {
    const [sum, setSum] = useState(propValue);
    const [stateValue, setStateValue] = useState(0);
    const onClick = useCallback(() => {
        console.log(propValue, stateValue, globalValue);
        setSum(propValue + stateValue + globalValue);
    }, []);
    return (
        <div>
            <p>State value is {stateValue}</p>
            <p>Sum is {sum}</p>
            <button onClick={() => setStateValue(stateValue + 1)}>
                Set stateValue
              </button>
            <button onClick={() => globalValue += 1}>
                Set globalValue
            </button>
            <button onClick={onClick}>
                Get Sum
            </button>
        </div>
    );
};
const Parent = () => {
    const [propValue, setPropValue] = useState(0);

    return (
        <div>
            <p>Prop value is {propValue}</p>
            <button onClick={() => setPropValue(propValue + 1)}>
                Set PropValue
            </button>
            <ComponentA propValue={propValue} />
        </div>
    );
};

If the dependencies list for useCallback in ComponentA is set to [], when the console.log is executed, no matter what the current propValue and stateValue are, they will always be 0. But the globalValue will always use the latest. When I check the __proto__ of propValue and stateValue, both of them are just primitive type.

How does useCallback hook keep the variables' original values? Shouldn't it respect the rule that if a variable is not defined in the current scope, it will look up to its parent and use the value from there (like globalValue)?

I understand that's the point of useCallback/useMemo but how is this implemented?

1 Answers

I found the answer from this blog https://dmitripavlutin.com/react-hooks-stale-closures/

Here is the simulate code for useCallback.

(function () {
    const ReactDomTree = {};
    const memo = {};
    const getButtonElement = () => { return ReactDomTree.buttonA };
    function useMemo(create) {
        if (!memo.result) {
            memo.result = create();
        }

        return memo.result;
    };

    const useCallback = function (callback) {
        return useMemo(() => callback);
    }


    function component() {
        let propValue = Math.floor(Math.random() * 100); // mimic prop update
        let stateValue = Math.floor(Math.random() * 100); // mimic state update
        console.log('component render', propValue, stateValue);

        const onClick = useCallback(() => {
            console.log('onclick', propValue, stateValue);
        });

        if (!ReactDomTree.buttonA) {
            ReactDomTree.buttonA = {};
            ReactDomTree.buttonA.onClick = onClick;
        }
    }

    component(); // component render
    let button = getButtonElement()
    button.onClick();
    component(); // component render
    button = getButtonElement()
    button.onClick() // This log result is same as the previous one

})();
Related