Imagine you're clearing out a bunch of unnecessary messages and you suddenly delete something important by mistake 😨. You instantly think, ‘Oh no!’ That’s when the undo functionality becomes a lifesaver 🚀. It allows you to reverse the action quickly and stress-free. Undo functionality isn’t just about reversing the action—it helps users explore without worrying about making permanent changes 🎉.
In this article, I will cover why undo functionality is crucial and where it’s most useful. Then, we’ll walk you through building a to-do list app in React with undo functionality. You'll see how to display an undo option whenever an item is deleted, and we’ll add a sound effect 🔊 to give immediate feedback when you delete or undo an action. Plus, we’ll include keyboard shortcuts ⌨️ like Ctrl + Z to make the process more efficient ⚡.
If you are a beginner, don't worry—we've explained every bit of code in clear and simple language, making it easy for anyone to follow. At the end of the article, you’ll find a link 🔗 to the full project source code, so you can explore and practice at your own pace 📝.
Stick around till the end, and I promise you'll leave with a solid understanding of how to implement undo functionality in your own projects 💼.
Let’s cut to the chase and jump right in—exciting discoveries await us! 🚀✨
Table of Contents
- What Is Undo Functionality and Why Is It Important?
- Which Types of Projects Should Implement Undo Functionality?
- How Do I Build Logic for This Project?
- Step-by-Step Guide to Building Undo Functionality in React
- Advanced Features to Enhance Your Undo Functionality
- The Necessity of Backend Support in Undo Functionality
What Is Undo Functionality and Why Is It Important?
Undo functionality allows users to reverse their most recent action, whether it was editing or deleting an item. If you want to understand this in the real world, Google Keep is a good example. It has a function for recently deleted notes. When you delete a to-do item, you can immediately undo the action.
This feature is important for making things easier in different ways, such as:
- User-Friendly: It allows users to quickly revert their actions; this way, it makes them feel relaxed.
- Builds Confidence: Users can test and try anything because they know they can undo any action.
- Fixing Errors: It helps users quickly correct mistakes without losing their work.
- Better Usability: Gives it the look and feel of simplicity and ease to use the application.
- Increases Satisfaction: A good undo feature enhances the usability of the application.
This feature is perfect for those who hit "delete" and then panic right away—not for those who delete today and think tomorrow! 😂. But don’t fret; I also have a solution for those kinds of folks too. I’ll share it later!
Which Types of Projects Should Implement Undo Functionality?
It should be noted that undo functionality can be implemented in any project in which users want to restore previously deleted items. It is particularly beneficial in some cases where mistakes can easily happen. Here are a few key projects where undo functionality is essential:
- To-Do List Apps: Give users the ability to restore their recently deleted tasks.
- Text Editors: Users should be able to revert to their previous action and delete or cancel a recently made modification.
- Project Management Tools: Develop the functionality for users to recover recently deleted tasks and comments.
- Social Media Applications: Provide the option to restore deleted posts or comments within a specific time frame.
- Customer Relationship Management (CRM) Systems: Allow users to revert to the most recent activity using customer information to avoid potential inaccuracy.
Apart from these, you can also implement this functionality in email clients, shopping carts, image editors, form builders, and game interfaces.
Now that we understand what undo functionality is, its importance, and where it can be used, let’s move on to building the project and implementing the undo feature in the To-do app. It will be easier for you to integrate it effectively if you follow this practical approach.
But hold on! To make things simpler, let's first create its logic. It’s not too complicated, but it’s always good practice to plan the structure before jumping into the project.
How Do I Build Logic for This Project?
To create a to-do list app with undo functionality, we’ll start by setting up some basic state variables. We need one array to store the current tasks and another to keep track of deleted tasks, allowing users to recover them later if needed. We’ll also add a state to show a snackbar notification when a task is deleted.
We will include audio feedback for deleted and undone actions, we will use the useRef hook to manage these audio effects.
We will create two main functions: deleteTask and undoDelete:
The deleteTask function will remove a task from the list, update the state, and store the deleted task for possible restoration. It will also show a snackbar notification and play a sound when a task is deleted for better UX.
The undoDelete function will restore a deleted task to its original position in the task list, remove it from the deleted tasks array, play a sound effect for the restoration, and display a confirmation notification.
To make users’ work convenient, we will assign a hot key Ctrl + Z that will allow users to reverse their last action. For this, we will use the useEffect hook to listen for key-down events.
Lastly, to give quick feedback, we will display a list of tasks, along with snackbar notifications.
Step-by-Step Guide to Building Undo Functionality in React
In this section, I will demonstrate how to add an undo feature in a basic React to-do list application. This will allow users to easily reverse accidental deletions. For attractive and responsive UI, we'll use the Material UI library. Let's break it down step by step, including all necessary code.
Prerequisites
- Familiarity with React components, hooks (useState, useEffect, useRef), and JSX.
- Knowledge of effectively using Material UI components.
- Ensure Node.js and npm are installed on your system.
Now set up your React development environment and install Material UI in that project using the commands below:
npx create-react-app todo-app-with-undo // For React App npm install @mui/material @mui/icons-material @emotion/react @emotion/styled // For MUI
Project Structure
After setting up your project, go to the src folder and create a new folder named assets for audio files. Then, create two files named TodoApp.jsx for your main application logic, and another TodoApp.css for styling.
Now you are ready to begin developing your application! It's time to work some magic!✨💻.
Step 1: Set Up Your Imports
First, we’ll import React, essential hooks, Material UI components, audio assets, and CSS files.
import React, { useState, useEffect, useRef, useCallback } from "react";
import { Snackbar, Button, IconButton } from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
import deleteSound from './assets/deletion.mp3';
import undoSound from './assets/undo.mp3';
import './TodoApp.css';
In this code, we import React hooks to manage state and lifecycle events in functional components. Next, we bring in Material UI components for interactive elements, followed by audio assets for sound feedback. Finally, we import the CSS file to style our UI.
Step 2: Set Up the Component
As a next step, we create our TodoApp component and define state variables for tasks, deleted tasks, snackbar notifications, and action messages.
const TodoApp = () => {
const [tasks, setTasks] = useState(["Task 1", "Task 2", "Task 3"]);
const [deletedTasks, setDeletedTasks] = useState([]);
const [snackbarOpen, setSnackbarOpen] = useState({});
const [actionMessage, setActionMessage] = useState("");
const SNACKBAR_DURATION = 5000;
Here in this code, we have defined multiple state variables to handle the various aspects of this application.
The tasks state holds the current list of to-do items. It will be where all your tasks are stored, and whenever a task is added or removed, this list will be updated.
The deletedTasks state keeps track of any tasks that have been deleted. Instead of permanently removing a task right away, we will store it here temporarily, allowing the user the option to restore it later using the "Undo" functionality.
The snackbarOpen state manages the visibility of snackbar notifications for each task. When you delete a task, this state is responsible for showing the snackbar with the "Undo" button.
The actionMessage state is used to display feedback messages to the user after certain actions, like when a task is successfully restored with "Undo." It will help the user understand what’s happening in the app.
Lastly, we defined a constant called SNACKBAR_DURATION to determine how long the snackbar message will be visible. It is set to 5000 ms (5 seconds). We can easily change the duration by just replacing the timing in one place if necessary.
Step 3: Prepare Sound Effects
We use useRef to create audio references for the delete and undo sounds.
const deleteSoundRef = useRef(new Audio(deleteSound));
const undoSoundRef = useRef(new Audio(undoSound));
In this code, deleteSoundRef is designated for playing a sound when a task is deleted, while undoSoundRef handles the sound played when a task is restored using the "Undo" option. These references make sure that the audio feedback is triggered smoothly during task actions.
Step 4: Create a Function to Play Sounds
Following that, we define a function for playing sound effects:
const playSound = (soundRef) => {
soundRef.current.currentTime = 0;
soundRef.current.play();
};
In this code, the playSound function takes a sound reference as an argument. It resets the audio playback to the beginning, ensuring that the sound plays from the start each time it's triggered. Then, it plays the audio that offers feedback immediately when a task is deleted or undone.
Step 5: Implement Task Deletion
We create a function to delete tasks and show a snackbar notification.
const deleteTask = (index) => {
const task = tasks[index];
setTasks(tasks.filter((_, i) => i !== index));
setDeletedTasks((prev) => [...prev, { task, index, key: Date.now() }]);
setSnackbarOpen((prev) => ({ ...prev, [task]: true }));
playSound(deleteSoundRef);
};
In this, deleteTask function deletes a selected task from the tasks array, and stores it under deletedTasks, so it can be restored later if needed. It also provides a snackbar notification to inform the user that a task has been deleted. Last of all, the function plays a sound that gives an audible alert of the specific action done.
Step 6: Implement Undo Functionality
To implement undo functionality, we'll create the unfoDelete function to restore deleted tasks.
const undoDelete = useCallback(({ task, index }) => {
setTasks(prev => [...prev.slice(0, index), task, ...prev.slice(index)]);
setDeletedTasks(prev => prev.filter(t => t.index !== index));
setActionMessage(`Restored: ${task}`);
setSnackbarOpen(prev => ({ ...prev, [task]: false }));
playSound(undoSoundRef);
},[]);
In the undoDelete function, we re-add the deleted task back into the tasks list, updating the state to reflect this change. We also removed the task from deletedTasks since it has been restored. An action message is set to inform the user with a sound effect that the task has been successfully restored, and then we dismiss the snackbar notification.
Step 7: Manage Snackbar Notifications
To control snackbar notifications, we'll use functions to open and close them after a task is deleted or an action is taken.
const closeSnackbar = (task) => setSnackbarOpen((prev) => ({ ...prev, [task]: false }));
const closeActionMessageSnackbar = () => setActionMessage("");
In this code, the closeSnackbar function closes the snackbar notification for a certain task when the user clicks on the close icon. The closeActionMessageSnackbar function clears any messages displayed to the user and maintains the clean usability of the interface.
Step 8: Handle Keyboard Shortcuts
To make our app more user-friendly, we add a keyboard shortcut to quickly undo the last action.
useEffect(() => {
const handleKeyDown = (event) => {
if (event.ctrlKey && event.key === 'z' && deletedTasks.length) {
event.preventDefault();
undoDelete(deletedTasks.at(-1));
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [deletedTasks, undoDelete]);
Here we set up a key press listener. If the user presses Ctrl + Z and there are deleted tasks, it will restore the latest deleted task on the list. We also clean up the event listener to avoid memory leaks. This assists users to revert actions easily without having to click the undo option.
Step 9: Render the Component
After, we will show the tasks and ensure that users can interact with them properly.
return (
<div className="todo-app-container">
<h1>To-Do List</h1>
<ul>
{tasks.map((task, index) => (
<li key={index}>
<span className="todo-app-task">{task}</span>
<Button className="todo-app-delete-btn" onClick={() => deleteTask(index)}>
Delete
</Button>
</li>
))}
</ul>
{deletedTasks.map((deletedTask, index) => (
<Snackbar
key={deletedTask.key}
open={snackbarOpen[deletedTask.task]}
message={`Deleted: ${deletedTask.task}`}
action={
<>
<Button className="todo-app-undo-btn" color="secondary" size="small" onClick={() => undoDelete(deletedTask)}>
UNDO
</Button>
<IconButton className="todo-app-close-btn" size="small" color="inherit" onClick={() => closeSnackbar(deletedTask.task)}>
<CloseIcon fontSize="small" />
</IconButton>
</>
}
autoHideDuration={SNACKBAR_DURATION}
onClose={() => closeSnackbar(deletedTask.task)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
/>
))}
<Snackbar
open={!!actionMessage}
message={actionMessage}
action={
<IconButton className="todo-app-close-action-btn" size="small" color="inherit" onClick={closeActionMessageSnackbar}>
<CloseIcon fontSize="small" />
</IconButton>
}
autoHideDuration={5000}
onClose={closeActionMessageSnackbar}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
/>
</div>
);
In this part, we display the list of tasks along with the delete buttons. It also has a feature of deleted task notifications displayed as snackbar notifications, so users can undo deletions or dismiss notifications.
Step 10: Integrate TodoApp Component in Main App Component
Let's integrate the TodoApp component into your main file, App.js.
import React from 'react'
import TodoApp from './TodoApp'
function App() {
return (
<TodoApp/>
)
}
export default App;
This component is the starting point of your application, rendering the TodoApp component.
Step 11: Add CSS for Styling
Last but not least, we need to add some styling to our app for better UI. Update your TodoApp.css file with the following styles:
/* TodoApp.css */
.todo-app-container {
max-width: 500px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
}
ul {
list-style: none;
padding: 0;
}
ul li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
margin: 10px 0;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* Delete Button */
.todo-app-delete-btn {
background-color: #ff5252 !important;
color: #fff !important;
}
.todo-app-delete-btn:hover {
background-color: #ff1e1e !important;
}
/* Snackbar Styles */
.MuiSnackbarContent-root {
background-color: #4caf50 !important;
color: #fff !important;
display: flex;
transition: background-color 0.3s ease;
}
.MuiSnackbarContent-root:hover {
background-color: #45a049 !important;
}
/* Undo Button */
.todo-app-undo-btn {
color: #fff !important;
font-weight: bold !important;
}
.todo-app-undo-btn:hover {
background-color: rgba(255, 255, 255, 0.2) !important;
}
/* Close Button */
.todo-app-close-btn,
.todo-app-close-action-btn {
padding: 4px;
transition: background-color 0.3s ease;
}
.todo-app-close-btn:hover,
.todo-app-close-action-btn:hover {
background-color: rgba(255, 255, 255, 0.2);
}
Congratulations! I hope you followed all the steps and successfully built a working to-do list app with an undo feature using React and Material UI. You can find the source code here.
Advanced Features to Enhance Your Undo Functionality
Incorporate advanced undo features into your application to make it more user-friendly and interesting. Here are some advanced features that can take your undo system to the next level:
Hover to Extend Snackbar Visibility
Add functionality to pause the snackbar timer when a user hovers over it, ensuring it stays visible until the cursor moves away. This way, they get more time to make a decision.
Undo All Recent Deletions
Implement an Undo All button that lets users restore all recently deleted tasks with a single action. This is very useful for users who may have a bad day and delete several items by mistake and want to recover them quickly.
Selective Task Recovery
As I mentioned earlier, I have a solution for those who realize, hours or even a day later, that what they deleted is exactly what they need now!
Implement selective task recovery so that if a user deletes multiple tasks at once, they can easily restore any important ones they need to recover. Provide a way to view deleted task history and set a grace period (like, 24 or 48 hours) for recovery, allowing users more time to decide before permanent removal.
Feedback Loop for Frequent Actions
Track user behavior with a feedback system, especially if actions are frequently undone. If a user consistently reverts to specific types of actions, the system can offer reminders or alternative suggestions. This tailored feedback helps users avoid repetitive mistakes and enhances the overall user experience.
Add Hover Label for Undo Button
Use a tooltip on the undo button that displays the message Ctrl + Z when users hover over it. It will inform the users about the keyboard shortcut for the undo action, which will help them to remember that there is a shortcut key to perform undo action to save time.
Incorporating these advanced features into the undo functionality will make your app user-friendly and quick to handle. It'll be easier to use, and people will appreciate knowing that you're making things simpler and more pleasant for them.
The Necessity of Backend Support in Undo Functionality
Implementing undo functionality only on the front end can lead to problems. For instance, in Gmail, if a user deletes a particular mail and at the same time the user closes the browser too quickly, the particular mail might even be permanently deleted before the undo action takes place. This may happen because sometimes the browser does not have enough time to exchange some data with the server before the user moves away.
Backend support is essential to making sure the undo feature works correctly. When you do an action, such as deleting a message, the system should make a request to the server to log it. The program keeps track of things you may want to undo later. The best part is that even if you go away or close your browser, you can still recover the deleted data. This backend support not only increases the application's reliability but also improves user experience by offering a standard approach to managing your actions.
Conclusion
In conclusion, implementing undo functionality is a game-changer for enhancing user experience in any application. By allowing users to easily reverse their actions, you foster a more engaging and confident environment where they can freely explore features without the fear of making irreversible mistakes. ✨
Through this article, we've highlighted the importance of the undo feature and walked you through creating a to-do list app in React that incorporates undo capabilities, complete with sound effects and keyboard shortcuts. 🎧 Remember, the key to a successful application lies not only in its core features but also in the thoughtful design of user interactions. 💡
If you liked this article, we would love to hear your thoughts in the comments below! Please feel free to share it with your friends. 🤗 If you have any questions about the article, don’t hesitate to ask in the question box, you will get the answers promptly. So go ahead—experiment, create, and make your applications amazing! Happy coding! 🚀👩💻👨💻
F&Qs
1. What’s the best way to notify users about the deletion?
Ans: When you want to notify the user that an item has been deleted or edited, use a snackbar or toast notification. It should confirm the action that has been performed and give the option of undoing it if needed.
2. How can I store deleted items temporarily?
Ans: You can use the local state management (like the useState hook) or a state management library (like Redux) to store deleted items for a while. Another option is to apply a timeout that locks the item in memory before deleting it permanently from the storage space.
3. How long should I wait before permanently deleting an item?
Ans: The ideal duration for undo options is 3 to 10 seconds, giving users enough time to respond without slowing down app performance. For historical features, a grace period of 24 to 48 hours is suggested to allow users to restore deleted items before permanent deletion.
4. What tools or libraries can I use to simplify the implementation of undo functionality?
Ans: Consider using libraries like Redux for state management and React Toastify for notifications. This tool and library make it easier to control undo actions and give feedback to the user.