I think that useEffect can be avoided here. Essentially what we are doing is to persist the state in query params, so whenever the user clicks on a node we change the query param (nodeid) and the other thing is if the user shares this URL with others we use this same nodeID to restore the tree view.
You can imagine the data structure to be a DAG Tree.
This looks like a good case for using useEffect (escape hatch from React and syncing React with URL - window) but I still think this is not the best way to do it.
const SELECTED_NODE_URL_PARAM = 'nodeId';
export const useNodeSelectionUrlSync = () => {
console.log('hook re-rendering');
const { setSearchedNodeWithAncestors } = useNodesData();
const { selectedNode, setSelectedNode } = useNodesUI();
// Restore selection from URL when the component mounts.
useEffect(() => {
console.log('useEffect 1 ran');
if (typeof window === 'undefined') return;
const url = new URL(window.location.href);
const linkedNodeId = url.searchParams.get(SELECTED_NODE_URL_PARAM);
if (!linkedNodeId) return;
setSearchedNodeWithAncestors(linkedNodeId)
.then((result) => {
if (result?.targetNode) {
setSelectedNode(result.targetNode);
} else {
url.searchParams.delete(SELECTED_NODE_URL_PARAM);
const search = url.searchParams.toString();
const nextUrl = ${url.pathname}${search ? ?${search} : ''}${url.hash};
window.history.replaceState(null, '', nextUrl);
}
})
.catch(() => {
url.searchParams.delete(SELECTED_NODE_URL_PARAM);
const search = url.searchParams.toString();
const nextUrl = ${url.pathname}${search ? ?${search} : ''}${url.hash};
window.history.replaceState(null, '', nextUrl);
});
}, [setSearchedNodeWithAncestors, setSelectedNode]);
// Keep the URL in sync with the currently selected node.
useEffect(() => {
console.log('useEffect 2 ran');
if (typeof window === 'undefined') return;
const url = new URL(window.location.href);
const currentNodeId = url.searchParams.get(SELECTED_NODE_URL_PARAM);
const selectedNodeId = selectedNode?.node.id;
if (selectedNodeId) {
if (currentNodeId === selectedNodeId) return;
url.searchParams.set(SELECTED_NODE_URL_PARAM, selectedNodeId);
} else if (currentNodeId) {
url.searchParams.delete(SELECTED_NODE_URL_PARAM);
} else {
return;
}
const search = url.searchParams.toString();
const nextUrl = ${url.pathname}${search ? ?${search} : ''}${url.hash};
window.history.replaceState(null, '', nextUrl);
}, [selectedNode]);
};