Most of us may have faced this situation, where we have made a change in an application and wished to go back to a previous version. Undo and redo features add this function to our application. They help users to go back to a previous version and restore the changes they made. We can find this feature in many applications like text editors, drawing tools, document editors, etc. The main idea behind the undo and redo buttons is to store the previous states and use them to undo or redo the data. In this blog, we will build a simple undo and redo feature with React and useState.
1. Create a React Project
First, we can create a React project. Use this command to create a React project with Vite.
npm create vite@latest react-undo-redo -- --template react
cd react-undo-redo
npm install
npm run dev
2. How Undo and Redo Work
Before developing our react component, let's understand how undo and redo work and their basic concepts. For an undo and redo function to work, we need to store the different versions of our application state. Mainly, we need three state values for this: past, present, and future. Past stores the previous state, present stores the current state, and future stores the states that can be restored.
3. Create the Editor Component
First, we can create the editor component. Create a new file named UndoRedo.jsx and add this code. This will create a simple text editor.
import { useState } from "react";
export default function UndoRedo() {
const [text, setText] = useState("");
return (
<div>
<textarea
value={text}
onChange={(event) => setText(event.target.value)}
placeholder="Start typing..."
/>
</div>
);
}4. Store the State History
Now, we can set up the states for undo and redo options. Update the component with this code.
const [history, setHistory] = useState({
past: [],
present: "",
future: [],
});Now, add the function to handle this state.
const handleChange = (event) => {
const newText = event.target.value;
setHistory((current) => ({
past: [...current.past, current.present],
present: newText,
future: [],
}));
};Update the textarea to connect the function.
<textarea
value={history.present}
onChange={handleChange}
placeholder="Start typing..."
/>
5. Implement the Undo Function
Now, we can set up the undo function. In an undo function, we need to take the latest past value and set it as the current value. Added this function to the component.
const undo = () => {
setHistory((current) => {
if (current.past.length === 0) {
return current;
}
const previous = current.past[current.past.length - 1];
return {
past: current.past.slice(0, -1),
present: previous,
future: [current.present, ...current.future],
};
});
};6. Implement the Redo Function
Next, we can create the redo function. Redo is the opposite of undo; we need to take the first value in the future state and set it in the current state. Add this function to the component.
const redo = () => {
setHistory((current) => {
if (current.future.length === 0) {
return current;
}
const next = current.future[0];
return {
past: [...current.past, current.present],
present: next,
future: current.future.slice(1),
};
});
};7. Add Undo and Redo Buttons
We have already added the functions for the undo and redo buttons. Now, we can add the buttons for them. Add this code to the component.
<div>
<button
onClick={undo}
disabled={history.past.length === 0}
>
Undo
</button>
<button
onClick={redo}
disabled={history.future.length === 0}
>
Redo
</button>
</div>
8. Complete Undo/Redo Component
import { useState } from "react";
export default function UndoRedo() {
const [history, setHistory] = useState({
past: [],
present: "",
future: [],
});
const handleChange = (event) => {
const newText = event.target.value;
setHistory((current) => ({
past: [...current.past, current.present],
present: newText,
future: [],
}));
};
const undo = () => {
setHistory((current) => {
if (current.past.length === 0) {
return current;
}
const previous = current.past[current.past.length - 1];
return {
past: current.past.slice(0, -1),
present: previous,
future: [current.present, ...current.future],
};
});
};
const redo = () => {
setHistory((current) => {
if (current.future.length === 0) {
return current;
}
const next = current.future[0];
return {
past: [...current.past, current.present],
present: next,
future: current.future.slice(1),
};
});
};
return (
<div className="mx-auto max-w-lg rounded-lg border p-6">
<h2 className="mb-4 text-lg font-semibold">
Simple Text Editor
</h2>
<textarea
value={history.present}
onChange={handleChange}
placeholder="Start typing..."
className="mb-4 h-40 w-full rounded-md border p-3"
/>
<div className="flex gap-3">
<button
onClick={undo}
disabled={history.past.length === 0}
className="rounded bg-blue-600 px-4 py-2 text-white disabled:opacity-50"
>
Undo
</button>
<button
onClick={redo}
disabled={history.future.length === 0}
className="rounded bg-blue-600 px-4 py-2 text-white disabled:opacity-50"
>
Redo
</button>
</div>
</div>
);
}Import our component into the App.jsx file to use it.
import UndoRedo from "./UndoRedo";
function App() {
return (
<div className="p-6">
<UndoRedo />
</div>
);
}
export default App;
Building undo and redo functions in a React application can become very easier when we understand the working of react states. By providing undo and redo buttons to our applications, it will be very useful for users to handle previous changes. It is a good practice to provide undo and redo functions for a text editor, document editor, and other applications.
To read more about How to Create an Image Preview Component in React, refer to our blog How to Create an Image Preview Component in React.