Search code examples
javascriptreactjstypescriptvisnetwork

Cannot access react state from callback


I have the following components:

const ParentComponent: React.FC = () => {
    // Somewhere in the code, I set this to some value
    const [newType, setNewType] = useState<any>(undefined);

    // Somewhere in the code, I set this to true
    const [enableAddEdge, setEnableAddEdge] = useState(false);

    const addEdge = (data: any) => {
        console.log("newType", newType); // This is undefined
    }

    return (
    ...
        <VisNetwork 
            newType={newType}
            onAddEdge={addEdge}
            enableAddEdge={enableAddEdge}
        />
    ...
    )
}
export default ParentComponent;

interface Props {
    newType: any;
    onAddEdge: (data: any) => void;
    enableAddEdge: boolean;
}
const VisNetwork: React.FC<Props> = (props: Props) => {
    const options: any = {
        // Some vis-network specific option configuration
        manipulation: {
            addEdge: (data: any, callback: any) => props.onAddEdge(data);
        }
    }
    ...
    // Some code to create the network and pass in the options
    const network = new vis.Network(container, networkData, options);

    useEffect(() => {
        if (props.enableAddEdge) {
            // This confirms that indeed newType has value
            console.log("newType from addEdge", props.newType);

            // Call reference to network (I name it currentNetwork)
            // This will enable the adding of edge in the network.  
            // When the user is done adding the edge,
            // the `addEdge` method in the `options.manipulation` will be called.
            currentNetwork.addEdgeMode();
        }
    }, [props.enableAddEdge])

    useEffect(() => {
        if (props.newType) {
            // This is just to confirm that indeed I am setting the newType value
            console.log("newType from visNetwork", props.newType); // This has value
        }
    }, [props.newType]);
}
export default VisNetwork;

When the addEdge method is called, the newType state becomes undefined. I know about the bind but I don't know if it's possible to use it and how to use it in a functional component. Please advise on how to obtain the newType value.

Also, from VisNetwork, I want to access networkData state from inside options.manipulation.addEdge. I know it's not possible but any workaround? I also need to access the networkData at this point.


Solution

  • You need to useRef in this scenario. It appears const network = new vis.Network(container, networkData, options); uses the options from the first render only. Or something similar is going on.

    It's likely to do with there being a closure around newType in the addEdge function. So it has stale values: https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function

    In order to combat this, you need to useRef to store the latest value of newType. The reference is mutable, so it can always store the current value of newType without re-rendering.

    // Somewhere in the code, I set this to some value
    const [newType, setNewType] = useState<any>(undefined);
    
    const newTypeRef = useRef(newType);
    useEffect(() => {
      // Ensure the ref is always at the latest value
      newTypeRef.current = newType;
    }, [newType])
    
    const addEdge = (data: any) => {
        console.log("newType", newTypeRef.current); // This is undefined
    }