Memento Pattern

The Memento pattern is a behavioral design pattern that allows you to capture and restore the internal state of an object without exposing its implementation details. It provides a way to save and restore the state of an object, enabling undo/redo functionality or reverting an object to a previous state.

You can take a quick look at how to use this pattern in React with functional components in the following example:

import React, { useState } from "react";
 
interface Memento {
  getState(): string;
}
 
class EditorMemento implements Memento {
  private state: string;
 
  constructor(state: string) {
    this.state = state;
  }
 
  getState(): string {
    return this.state;
  }
}
 
interface Editor {
  getContent(): string;
  setContent(content: string): void;
  save(): Memento;
  restore(memento: Memento): void;
}
 
const TextEditor: React.FC = () => {
  const [content, setContent] = useState("");
  const [history, setHistory] = useState<Memento[]>([]);
 
  const save = () => {
    const memento: Memento = new EditorMemento(content);
    setHistory((prevHistory) => [...prevHistory, memento]);
  };
 
  const undo = () => {
    if (history.length > 0) {
      const previousMemento = history[history.length - 1];
      setHistory((prevHistory) => prevHistory.slice(0, prevHistory.length - 1));
      setContent(previousMemento.getState());
    }
  };
 
  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    setContent(e.target.value);
  };
 
  return (
    <div>
      <textarea value={content} onChange={handleChange}></textarea>
      <button onClick={save}>Save</button>
      <button onClick={undo}>Undo</button>
    </div>
  );
};
 
export default TextEditor;
import React, { useState } from "react";
 
interface Memento {
  getState(): string;
}
 
class EditorMemento implements Memento {
  private state: string;
 
  constructor(state: string) {
    this.state = state;
  }
 
  getState(): string {
    return this.state;
  }
}
 
interface Editor {
  getContent(): string;
  setContent(content: string): void;
  save(): Memento;
  restore(memento: Memento): void;
}
 
const TextEditor: React.FC = () => {
  const [content, setContent] = useState("");
  const [history, setHistory] = useState<Memento[]>([]);
 
  const save = () => {
    const memento: Memento = new EditorMemento(content);
    setHistory((prevHistory) => [...prevHistory, memento]);
  };
 
  const undo = () => {
    if (history.length > 0) {
      const previousMemento = history[history.length - 1];
      setHistory((prevHistory) => prevHistory.slice(0, prevHistory.length - 1));
      setContent(previousMemento.getState());
    }
  };
 
  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    setContent(e.target.value);
  };
 
  return (
    <div>
      <textarea value={content} onChange={handleChange}></textarea>
      <button onClick={save}>Save</button>
      <button onClick={undo}>Undo</button>
    </div>
  );
};
 
export default TextEditor;

In this example, we have a simple TextEditor component that demonstrates the Memento pattern. The TextEditor component allows users to type and edit text in a textarea. It provides a "Save" button to capture the current content state as a memento, and an "Undo" button to restore the previous content state from the history of mementos.

Use cases

By applying the Memento pattern, you can easily implement features such as undo/redo functionality, checkpointing, and state management. It provides a convenient way to capture and restore object states, enabling users to navigate and revert to previous states in an application.

The Memento pattern is particularly useful in scenarios as:

1. Undo / Redo:

Allowing users to undo and redo actions in an application, restoring objects to previous states.

import React, { useState } from "react";
 
interface Memento {
  getState(): string;
}
 
class EditorMemento implements Memento {
  private state: string;
 
  constructor(state: string) {
    this.state = state;
  }
 
  getState(): string {
    return this.state;
  }
}
 
interface Editor {
  getContent(): string;
  setContent(content: string): void;
  save(): Memento;
  restore(memento: Memento): void;
}
 
const TextEditor: React.FC = () => {
  const [content, setContent] = useState("");
  const [history, setHistory] = useState<Memento[]>([]);
  const [currentIndex, setCurrentIndex] = useState<number>(-1);
 
  const save = () => {
    const memento: Memento = new EditorMemento(content);
    const newHistory = history.slice(0, currentIndex + 1);
    setHistory([...newHistory, memento]);
    setCurrentIndex(currentIndex + 1);
  };
 
  const undo = () => {
    if (currentIndex > 0) {
      setCurrentIndex(currentIndex - 1);
      const previousMemento = history[currentIndex - 1];
      setContent(previousMemento.getState());
    }
  };
 
  const redo = () => {
    if (currentIndex < history.length - 1) {
      setCurrentIndex(currentIndex + 1);
      const nextMemento = history[currentIndex + 1];
      setContent(nextMemento.getState());
    }
  };
 
  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    setContent(e.target.value);
  };
 
  return (
    <div>
      <textarea value={content} onChange={handleChange}></textarea>
      <button onClick={save}>Save</button>
      <button onClick={undo}>Undo</button>
      <button onClick={redo}>Redo</button>
    </div>
  );
};
 
export default TextEditor;
import React, { useState } from "react";
 
interface Memento {
  getState(): string;
}
 
class EditorMemento implements Memento {
  private state: string;
 
  constructor(state: string) {
    this.state = state;
  }
 
  getState(): string {
    return this.state;
  }
}
 
interface Editor {
  getContent(): string;
  setContent(content: string): void;
  save(): Memento;
  restore(memento: Memento): void;
}
 
const TextEditor: React.FC = () => {
  const [content, setContent] = useState("");
  const [history, setHistory] = useState<Memento[]>([]);
  const [currentIndex, setCurrentIndex] = useState<number>(-1);
 
  const save = () => {
    const memento: Memento = new EditorMemento(content);
    const newHistory = history.slice(0, currentIndex + 1);
    setHistory([...newHistory, memento]);
    setCurrentIndex(currentIndex + 1);
  };
 
  const undo = () => {
    if (currentIndex > 0) {
      setCurrentIndex(currentIndex - 1);
      const previousMemento = history[currentIndex - 1];
      setContent(previousMemento.getState());
    }
  };
 
  const redo = () => {
    if (currentIndex < history.length - 1) {
      setCurrentIndex(currentIndex + 1);
      const nextMemento = history[currentIndex + 1];
      setContent(nextMemento.getState());
    }
  };
 
  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    setContent(e.target.value);
  };
 
  return (
    <div>
      <textarea value={content} onChange={handleChange}></textarea>
      <button onClick={save}>Save</button>
      <button onClick={undo}>Undo</button>
      <button onClick={redo}>Redo</button>
    </div>
  );
};
 
export default TextEditor;

In this example, we have a TextEditor component that allows users to type and edit text in a textarea. The component provides "Save," "Undo," and "Redo" buttons. The TextEditor component uses an array of mementos to keep track of the state history. When the user clicks the "Save" button, a new memento is created and added to the history array. The "Undo" and "Redo" buttons allow the user to navigate through the history and restore previous states.

2. Checkpoint / Save Points:

Capturing checkpoints or save points in an application's workflow to enable the ability to return to a specific state.

import React, { useState } from "react";
 
interface Memento {
  getState(): string;
}
 
class WorkflowMemento implements Memento {
  private state: string;
 
  constructor(state: string) {
    this.state = state;
  }
 
  getState(): string {
    return this.state;
  }
}
 
interface Workflow {
  getStep(): string;
  setStep(step: string): void;
  saveCheckpoint(): Memento;
  restoreCheckpoint(memento: Memento): void;
}
 
const WorkflowManager: React.FC = () => {
  const [currentStep, setCurrentStep] = useState("");
  const [checkpoints, setCheckpoints] = useState<Memento[]>([]);
 
  const saveCheckpoint = () => {
    const memento: Memento = new WorkflowMemento(currentStep);
    setCheckpoints([...checkpoints, memento]);
  };
 
  const restoreCheckpoint = (memento: Memento) => {
    setCurrentStep(memento.getState());
  };
 
  const handleStepChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setCurrentStep(e.target.value);
  };
 
  const renderCheckpoints = () => {
    return checkpoints.map((checkpoint, index) => (
      <button key={index} onClick={() => restoreCheckpoint(checkpoint)}>
        Checkpoint {index + 1}
      </button>
    ));
  };
 
  return (
    <div>
      <input type='text' value={currentStep} onChange={handleStepChange} />
      <button onClick={saveCheckpoint}>Save Checkpoint</button>
      <div>
        <h4>Checkpoints</h4>
        {renderCheckpoints()}
      </div>
    </div>
  );
};
 
export default WorkflowManager;
import React, { useState } from "react";
 
interface Memento {
  getState(): string;
}
 
class WorkflowMemento implements Memento {
  private state: string;
 
  constructor(state: string) {
    this.state = state;
  }
 
  getState(): string {
    return this.state;
  }
}
 
interface Workflow {
  getStep(): string;
  setStep(step: string): void;
  saveCheckpoint(): Memento;
  restoreCheckpoint(memento: Memento): void;
}
 
const WorkflowManager: React.FC = () => {
  const [currentStep, setCurrentStep] = useState("");
  const [checkpoints, setCheckpoints] = useState<Memento[]>([]);
 
  const saveCheckpoint = () => {
    const memento: Memento = new WorkflowMemento(currentStep);
    setCheckpoints([...checkpoints, memento]);
  };
 
  const restoreCheckpoint = (memento: Memento) => {
    setCurrentStep(memento.getState());
  };
 
  const handleStepChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setCurrentStep(e.target.value);
  };
 
  const renderCheckpoints = () => {
    return checkpoints.map((checkpoint, index) => (
      <button key={index} onClick={() => restoreCheckpoint(checkpoint)}>
        Checkpoint {index + 1}
      </button>
    ));
  };
 
  return (
    <div>
      <input type='text' value={currentStep} onChange={handleStepChange} />
      <button onClick={saveCheckpoint}>Save Checkpoint</button>
      <div>
        <h4>Checkpoints</h4>
        {renderCheckpoints()}
      </div>
    </div>
  );
};
 
export default WorkflowManager;

In this example, we have a WorkflowManager component that allows users to manage a workflow by setting the current step. The component provides a "Save Checkpoint" button to capture the current step as a memento and a list of buttons to restore checkpoints. The WorkflowManager component uses an array of mementos to store the checkpoints. When a user clicks a checkpoint button, the corresponding memento is retrieved, and the step is restored.

3. State Management:

Managing and restoring the state of complex objects, enabling rollback or recovery in case of errors or failures.

import React, { useState } from "react";
 
interface Memento {
  getState(): CounterState;
}
 
interface CounterState {
  count: number;
}
 
class CounterMemento implements Memento {
  private state: CounterState;
 
  constructor(state: CounterState) {
    this.state = state;
  }
 
  getState(): CounterState {
    return this.state;
  }
}
 
interface Counter {
  getCount(): number;
  increment(): void;
  decrement(): void;
  saveState(): Memento;
  restoreState(memento: Memento): void;
}
 
const CounterComponent: React.FC = () => {
  const [count, setCount] = useState(0);
  const [history, setHistory] = useState<Memento[]>([]);
 
  const increment = () => {
    setCount((prevCount) => prevCount + 1);
  };
 
  const decrement = () => {
    setCount((prevCount) => prevCount - 1);
  };
 
  const saveState = () => {
    const memento: Memento = new CounterMemento({ count });
    setHistory((prevHistory) => [...prevHistory, memento]);
  };
 
  const restoreState = (memento: Memento) => {
    const { count } = memento.getState();
    setCount(count);
  };
 
  return (
    <div>
      <h3>Counter: {count}</h3>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={saveState}>Save State</button>
      <div>
        <h4>History</h4>
        {history.map((memento, index) => (
          <button key={index} onClick={() => restoreState(memento)}>
            Restore State {index + 1}
          </button>
        ))}
      </div>
    </div>
  );
};
 
export default CounterComponent;
import React, { useState } from "react";
 
interface Memento {
  getState(): CounterState;
}
 
interface CounterState {
  count: number;
}
 
class CounterMemento implements Memento {
  private state: CounterState;
 
  constructor(state: CounterState) {
    this.state = state;
  }
 
  getState(): CounterState {
    return this.state;
  }
}
 
interface Counter {
  getCount(): number;
  increment(): void;
  decrement(): void;
  saveState(): Memento;
  restoreState(memento: Memento): void;
}
 
const CounterComponent: React.FC = () => {
  const [count, setCount] = useState(0);
  const [history, setHistory] = useState<Memento[]>([]);
 
  const increment = () => {
    setCount((prevCount) => prevCount + 1);
  };
 
  const decrement = () => {
    setCount((prevCount) => prevCount - 1);
  };
 
  const saveState = () => {
    const memento: Memento = new CounterMemento({ count });
    setHistory((prevHistory) => [...prevHistory, memento]);
  };
 
  const restoreState = (memento: Memento) => {
    const { count } = memento.getState();
    setCount(count);
  };
 
  return (
    <div>
      <h3>Counter: {count}</h3>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={saveState}>Save State</button>
      <div>
        <h4>History</h4>
        {history.map((memento, index) => (
          <button key={index} onClick={() => restoreState(memento)}>
            Restore State {index + 1}
          </button>
        ))}
      </div>
    </div>
  );
};
 
export default CounterComponent;

In this example, we have a CounterComponent that manages a counter state. The component allows users to increment or decrement the counter. It provides a "Save State" button to capture the current counter state as a memento and a list of buttons to restore previous states from the history. The CounterComponent maintains a history of mementos to enable the rollback or recovery of the counter state.

Advantages

Disadvantages