Blog>
Snippets

Undo and Redo with useReducer

Implement an undo and redo functionality for a text editor by using useReducer to manage state history and control state changes.
import React, { useReducer } from 'react';

// Action Types
const ACTION_TYPES = {
  CHANGE: 'CHANGE',
  UNDO: 'UNDO',
  REDO: 'REDO'
};

// Reducer to handle actions
const reducer = (state, action) => {
  const { past, present, future } = state;
  switch (action.type) {
    case ACTION_TYPES.CHANGE:
      return {
        past: [...past, present],
        present: action.newText,
        future: []
      };
    case ACTION_TYPES.UNDO:
      if (past.length === 0) return state;
      return {
        past: past.slice(0, -1),
        present: past[past.length - 1],
        future: [present, ...future]
      };
    case ACTION_TYPES.REDO:
      if (future.length === 0) return state;
      return {
        past: [...past, present],
        present: future[0],
        future: future.slice(1)
      };
    default:
      return state;
  }
};

const initialState = { past: [], present: '', future: [] };
Defines the initial state and reducer function, action types and handler logic for change, undo, and redo actions.
const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { past, present, future } = state;

  const handleChange = (newText) => {
    dispatch({ type: ACTION_TYPES.CHANGE, newText });
  };

  const handleUndo = () => {
    dispatch({ type: ACTION_TYPES.UNDO });
  };

  const handleRedo = () => {
    dispatch({ type: ACTION_TYPES.REDO });
  };

  return (
    <div>
      <button onClick={handleUndo} disabled={past.length === 0}>Undo</button>
      <button onClick={handleRedo} disabled={future.length === 0}>Redo</button>
      <textarea value={present} onChange={(e) => handleChange(e.target.value)} />
    </div>
  );
};

export default App;
The App component sets up the useReducer hook with the reducer and initial state. It provides event handlers to change the text, undo and redo changes.