I'm building a React application with a tree component. When a user clicks on a root node, I want to automatically display the first child item of that node.
When users click too quickly on a node, they may click before the API has finished fetching the child items. This creates a race condition where I try to access tree[0].items[0]
but tree[0].items
is still empty.
I've implemented a polling approach to wait for the data:
const getFirstItemAsync = useCallback(async () => {
while (isEmpty(tree?.[0]?.items)) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
return tree?.[0]?.items?.[0];
}, [tree]);
Here's how I'm fetching the tree data:
const loadItemsFn = useCallback(
(item, targetId, isExpand = true) => {
if (loadItems) {
loadItems(params).then(
(data) => {
setTree(data)
}
);
}
},
[loadItems, tree]
);
I believe there should be a cleaner way to track when the API call completes instead of polling with setTimeout. How can I refactor this to properly wait for the API response before trying to access the first item?
You could do something like the following
const [isTreeLoading, setIsTreeLoading] = useState(false);
const loadItemsFn = useCallback(
(item: A, targetId: string, isExpand: boolean = true) => {
if (loadItems) {
setIsTreeLoading(true);
loadItems(params).then(
(data: ITreePayload<ITreeItem>) => {
setTree(data)
}
).finally(()=>{
setIsTreeLoading(false);
})
}
},
[loadItems, tree ]
);
// now you can use the isTreeLoading variable whenever you want to
This is a initial approach to this issue, just to give the general idea. In reality you should have to check/handle for errors of the request etc.
There are libraries that can provide much of this out of the box, like TanStack query (previously known as react-query)