diff --git a/.changeset/funny-shrimps-remain.md b/.changeset/funny-shrimps-remain.md new file mode 100644 index 00000000..628dc865 --- /dev/null +++ b/.changeset/funny-shrimps-remain.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Fixed number of issues connected to batching node & edge updates. diff --git a/packages/react/src/components/BatchProvider/useQueue.ts b/packages/react/src/components/BatchProvider/useQueue.ts index 4170fa7b..c33208d7 100644 --- a/packages/react/src/components/BatchProvider/useQueue.ts +++ b/packages/react/src/components/BatchProvider/useQueue.ts @@ -13,28 +13,21 @@ import { Queue, QueueItem } from './types'; */ export function useQueue(runQueue: (items: QueueItem[]) => 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(() => setShouldFlush(true))); + const [queue] = useState(() => createQueue(() => 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(runQueue: (items: QueueItem[]) => 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; }