useRef current getting its value only on second update
Asked Answered
C

3

6

I have the following components:

const ParentComponent: React.FC = () => {
    const networkRef: any = useRef();

    // Somewhere in the code, I call this
    networkRef.current.filter(["id0, id1, id2"]);

    return (
    ...
        <VisNetwork 
            ref={networkRef}
        />
    ...
    )
}
export default ParentComponent;

interface Props {
    ref: any;
}
const VisNetwork: React.FC<Props> = forwardRef((props: Props, ref) => {
    useImperativeHandle(ref, () => ({
        filter(items: any) {
            setFilterNodes(items);
            nView.refresh();
        }
    }));

    const [filterNodes, setFilterNodes] = useState<any[]>([]);
    const filterNodesRef = useRef(filterNodes);
    useEffect(() => {
        filterNodesRef.current = filterNodes;
    }, [filterNodes]);

    ...
    // Some code to create the network (concentrate on the nodesView filter method)
    const [nView, setNView] = useState<DataView>();
    const nodesView = new DataView(nodes, {
        filter: (n: any) => {
            if (filterNodesRef.current.includes(n.id)) {
                return true;
            }
            return false;
        }
    })
    setNView(nodesView);
    const network = new vis.Network(container, {nodes: nodesView, edges: edgesView}, options);
});
export default VisNetwork;

WHen I call network.current.filter([...]), it will set the filterNodes state. Also, it should set the filterNodesRef inside the useEffect.

However, the filterNodesRef.current remains to be empty array.

But when I call network.current.filter([...]) the second time, only then the filterNodesRef.current got the value and the DataView was able to filter.

Why is it like this? I thought the useRef.current will always contain the latest value.

Cristinacristine answered 5/6, 2020 at 11:33 Comment(0)
C
4

I finally solved this by calling the refresh() method inside the useEffect instead of the filter() method:

useEffect(() => {
    filterNodesRef.current = filterNodes;
    nView.refresh();
}, [filterNodes]);
Cristinacristine answered 8/6, 2020 at 3:0 Comment(0)
I
2

Settings the .current of a reference does not notify the component about the changes. There must be some other reason why it works the second time.

From https://reactjs.org/docs/hooks-reference.html#useref

Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.

You may want to use useState, as this does rerender the component.

Two more things

  1. I'm not really sure what networkRef.current.filter(["id0, id1, id2"]) is. Typescript does complain when I try to do ['a'].filter(['a']) and I've never seen this, so are you sure this is what you wanted to do?

  2. If you're passing references around there's probably a better way to do it. Maybe consider re-thinking the relations between your components. Are you doing this because you need access to networkRef inside multiple components? If yes, you might want to look at providers.

If this does not answer your question, write a comment (about something specific please) and I'll be happy to try and help you with it :)

Idolah answered 5/6, 2020 at 11:53 Comment(4)
i need to pass an array in network.current.filter, because the filter method in the DataView has this checking filterNodesRef.current.includes(n.id)Cristinacristine
@Cristinacristine Am I correct in the assumption that this is not a standard array? Because if it is, you should be getting compiler errors.Idolah
@Cristinacristine Did it solve your problem otherwise?Idolah
@Cristinacristine then you should provide more detail about what your issue is :)Idolah
P
0

Yes, useRef.current contains latest value, but your filterNodesRef.current in a useEffect that's why you get empty array in initial render.

  • Initial render of VisNetwork the filterNodes is an empty array ==> filterNodesRef.current remains empty. Because setFilterNodes(items); is asyn function => event you set it in useImperativeHandle it will be updated in second render.

  • In useImperativeHandle you set setFilterNodes(items); ==> filterNodes is updated and the VisNetwork re-render ==> useEffect is triggered ==> filterNodesRef.current is set to new filterNodes

Let's try this:

.... 
const filterNodesRef = useRef(filterNodes);
useImperativeHandle(ref, () => ({
    filter(items: any) {
      filterNodesRef.current = filterNodes;
      setFilterNodes(items);
      nView.refresh();
    }
}));
...
Pelecypod answered 8/6, 2020 at 1:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.