Skip to main content

How to implement undo/redo on canvas with React?

To implement undo/redo functionality with React you don't need to use Konva's serialization and deserialization methods.

You just need to save a history of all the state changes within your app. There are many ways to do this. It may be simpler to do that if you use immutable structures.

Instructions: Try to move the square by dragging it. Then use the "undo" and "redo" buttons to revert or replay your actions.

import React, { Component } from 'react';
import { Stage, Layer, Rect, Text } from 'react-konva';


const App = () => {
const [position, setPosition] = React.useState({ x: 20, y: 20 });
// We use refs to keep history to avoid unnecessary re-renders
const history = React.useRef([{ x: 20, y: 20 }]);
const historyStep = React.useRef(0);

const handleUndo = () => {
if (historyStep.current === 0) {
return;
}
historyStep.current -= 1;
const previous = history.current[historyStep.current];
setPosition(previous);
};

const handleRedo = () => {
if (historyStep.current === history.current.length - 1) {
return;
}
historyStep.current += 1;
const next = history.current[historyStep.current];
setPosition(next);
};

const handleDragEnd = (e) => {
// Remove all states after current step
history.current = history.current.slice(0, historyStep.current + 1);
const pos = {
x: e.target.x(),
y: e.target.y(),
};
// Push the new state
history.current = history.current.concat([pos]);
historyStep.current += 1;
setPosition(pos);
};

return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Text text="undo" onClick={handleUndo} />
<Text text="redo" x={40} onClick={handleRedo} />
<Rect
x={position.x}
y={position.y}
width={50}
height={50}
fill="black"
draggable
onDragEnd={handleDragEnd}
/>
</Layer>
</Stage>
);
};

export default App;

Where a hand-built history stops

The history above records one value per step. A production editor has to record grouped operations, so that a multi-select drag undoes as a single step, plus transforms and images that finish loading after the action. That state machine usually grows larger than the drawing code, so plan the history around document operations rather than around raw node state.