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.
3 Answers
Reset to default 4I 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
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?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
thefilterNodes
is an empty array ==>filterNodesRef.current
remains empty. BecausesetFilterNodes(items);
is asyn function => event you set it inuseImperativeHandle
it will be updated in second render.In
useImperativeHandle
you setsetFilterNodes(items);
==> filterNodes is updated and theVisNetwork
re-render ==>useEffect
is triggered ==>filterNodesRef.current
is set to newfilterNodes
Let's try this:
....
const filterNodesRef = useRef(filterNodes);
useImperativeHandle(ref, () => ({
filter(items: any) {
filterNodesRef.current = filterNodes;
setFilterNodes(items);
nView.refresh();
}
}));
...