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;