Vivek Jalondhara. All writing

Frontend

Undo and Redo in React Flow: Save Actions, Not Pointer Events

By Vivek Jalondhara · 5 min read
Published

Build diagram history around complete editing actions, including grouped movement, connected deletions, and predictable redo behavior.

Dragging one device across a floor plan may generate many position updates. A history stack that records each update forces the user to press Undo repeatedly to reverse one movement. Recording too little is also dangerous: restoring a device without its deleted connections produces a different design.

This educational example uses the AV Design and Engineering Suite’s equipment and signal-flow domain. It describes a proposed single-user history model around React Flow, without claiming that the project uses this exact implementation.

Choose the document that history owns

Define a canonical editable document containing placements, connections, and any domain changes that must travel together. Keep hover, selection rectangles, measured node dimensions, and pointer coordinates outside that document unless the product explicitly treats them as persistent edits.

For an AV design, removing a placement and removing a physical equipment record are different actions. Decide which one Delete means in each view. Undo must restore all entities changed by that action, including related port references or connections, while leaving unrelated catalog data alone.

Wrap a gesture in one transaction

At drag start, remember the committed document. During movement, update a temporary working view so the canvas remains responsive. At drag end, compare the meaningful coordinates and commit one new snapshot. If the user cancels the gesture, restore the original view without creating a history entry.

Apply the same boundary to a multi-node drag, paste operation, or keyboard movement sequence. The grouping policy should match how a person describes the action. A timer alone is insufficient: two different edits made quickly should not accidentally become one undo step.

Keep React Flow changes and history distinct

A controlled React Flow canvas receives node and edge state from the application and reports changes through callbacks. Those callbacks are an input stream, not an automatic definition of the document’s history. Selection and measurement updates can arrive alongside edits that deserve persistence.

Use an adapter that applies relevant changes to the working view and dispatches semantic commits to the history reducer. When undo changes the canonical document, derive both node and edge props from that same restored snapshot. Maintaining separate independent histories for the two arrays can create dangling edges.

Invalidate redo when a new branch begins

History can be represented as past, present, and future snapshots. Undo moves the current document into future; redo moves it back into past. Committing a new edit after undo clears future because the user has created a different continuation.

The small reducer below caps retained past snapshots and ignores commits that reuse the present object. Callers must also suppress equivalent newly allocated snapshots, such as a drag that finishes where it started. All retained documents must be treated as immutable; mutating a shared nested object changes history retroactively.

Keep undo separate from save status

Undo changes the local document, which may need to be saved just like any other edit. It does not cancel an already accepted server write or erase an audit event. Serialize autosaves or use revision-aware writes so a delayed response cannot overwrite the restored document.

If saving fails, retain the restored local state and show that it remains unsaved. Decide whether history survives switching sheets or reopening a project. Clearing history on navigation is simpler; preserving it requires the history owner to remain tied to the correct document identity.

Verify the boundaries with small designs

Test moving two connected devices, deleting one device and its edges, undoing that deletion, and then making a new connection before trying redo. Also test a cancelled drag and a movement that returns to its starting position. Inspect the domain references as well as the visible canvas.

Whole-document snapshots are easy to reason about but consume memory as designs grow. Commands or reversible patches can reduce that cost at the price of more complicated inverses. Concurrent editing needs an additional conflict-aware design; replacing a full snapshot could otherwise remove someone else’s work.

A bounded history reducer for committed documents

Pass complete immutable documents after a gesture finishes. This original reducer is independent of React Flow’s paid example; the integration must group events and exclude transient UI state.

Illustrative example · TypeScript · Simplified for explanation

type Timeline<T> = { past: T[]; present: T; future: T[] };
type Edit<T> = { kind: "commit"; document: T } |
  { kind: "undo" } | { kind: "redo" };

function reduceHistory<T extends object>(h: Timeline<T>, edit: Edit<T>): Timeline<T> {
  if (edit.kind === "commit") {
    if (edit.document === h.present) return h;
    return { past: [...h.past, h.present].slice(-50),
      present: edit.document, future: [] };
  }
  if (edit.kind === "undo") {
    const previous = h.past[h.past.length - 1];
    if (previous === undefined) return h;
    return { past: h.past.slice(0, -1), present: previous,
      future: [h.present, ...h.future] };
  }
  const next = h.future[0];
  if (next === undefined) return h;
  return { past: [...h.past, h.present].slice(-50), present: next,
    future: h.future.slice(1) };
}
How the example fits together
  1. Start one editing action
  2. Update a temporary canvas view
  3. Commit the complete document once
  4. Undo or redo the document atomically

Conceptual flow for the example above.

Tradeoff to keep in mind

Snapshots keep inverse behavior simple but retain document data in memory. The entry cap is illustrative; choose a limit from representative document sizes and editing sessions.

Related project: AV Design & Engineering Suite →

References & further reading

Related posts