Merge pull request #4796 from Aki-7/fix/useQueue

Fix deadlock in useQueue
This commit is contained in:
Peter Kogo
2024-11-08 11:26:21 +01:00
committed by GitHub
2 changed files with 12 additions and 18 deletions

View File

@@ -0,0 +1,5 @@
---
'@xyflow/react': patch
---
Fixed number of issues connected to batching node & edge updates.

View File

@@ -13,28 +13,21 @@ import { Queue, QueueItem } from './types';
*/
export function useQueue<T>(runQueue: (items: QueueItem<T>[]) => void) {
// Because we're using a ref above, we need some way to let React know when to
// actually process the queue. We flip this bit of state to `true` any time we
// mutate the queue and then flip it back to `false` after flushing the queue.
const [shouldFlush, setShouldFlush] = useState(false);
// actually process the queue. We increment this number any time we mutate the
// queue, creating a new state to trigger the layout effect below.
// Using a boolean dirty flag here instead would lead to issues related to
// automatic batching. (https://github.com/xyflow/xyflow/issues/4779)
const [serial, setSerial] = useState(BigInt(0));
// A reference of all the batched updates to process before the next render. We
// want a reference here so multiple synchronous calls to `setNodes` etc can be
// batched together.
const [queue] = useState(() => createQueue<T>(() => setShouldFlush(true)));
const [queue] = useState(() => createQueue<T>(() => setSerial(n => n + BigInt(1))));
// Layout effects are guaranteed to run before the next render which means we
// shouldn't run into any issues with stale state or weird issues that come from
// rendering things one frame later than expected (we used to use `setTimeout`).
useIsomorphicLayoutEffect(() => {
// Because we need to flip the state back to false after flushing, this should
// trigger the hook again (!). If the hook is being run again we know that any
// updates should have been processed by now and we can safely clear the queue
// and bail early.
if (!shouldFlush) {
queue.reset();
return;
}
const queueItems = queue.get();
if (queueItems.length) {
@@ -42,11 +35,7 @@ export function useQueue<T>(runQueue: (items: QueueItem<T>[]) => void) {
queue.reset();
}
// Because we're using reactive state to trigger this effect, we need to flip
// it back to false.
setShouldFlush(false);
}, [shouldFlush]);
}, [serial]);
return queue;
}