最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - useRef current getting its value only on second update - Stack Overflow

programmeradmin5浏览0评论

I have the following ponents:

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.

I have the following ponents:

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.

Share Improve this question edited Jun 8, 2020 at 0:05 iPhoneJavaDev asked Jun 5, 2020 at 11:33 iPhoneJavaDeviPhoneJavaDev 8256 gold badges38 silver badges86 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 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]);

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

From https://reactjs/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 ponent.

Two more things

  1. I'm not really sure what networkRef.current.filter(["id0, id1, id2"]) is. Typescript does plain 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 ponents. Are you doing this because you need access to networkRef inside multiple ponents? If yes, you might want to look at providers.

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

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();
    }
}));
...

发布评论

评论列表(0)

  1. 暂无评论