如何为React CRUD表格添加撤销功能并修复删除操作时Toast通知不显示问题
Let's work through your two issues step by step—getting the toast to show up, and adding that undo functionality similar to the example you referenced.
1. Fixing the Missing Toast Notification
The main problem here is where you've placed the toast() call: inside the filter callback. The filter method is meant to be a pure function (no side effects like showing toasts) that just decides which items to keep in the array. Your toast was getting lost in that logic, hence why it wasn't displaying.
Here's how to adjust the deleteCustomer function to trigger the toast correctly:
const deleteCustomer = (ID) => { if (window.confirm("Are you sure you want to delete?")) { Axios.delete(`http://localhost:3001/stats/delete/${ID}`).then( (response) => { // First, update the customer list setCustomerList(prevList => prevList.filter(val => val.ID !== ID)); // Now trigger the toast outside the filter callback toast("Customer deleted!"); console.log("toast notify run"); } ); } };
This moves the toast to a spot where it runs right after the state is updated, ensuring it fires reliably.
2. Adding Undo Functionality
The core idea here is:
- Keep a reference to the deleted customer so we can restore it
- Show a toast with an undo button that gives the user a window to revert the action
- Cancel the backend delete request if the user undoes (optional but clean)
- Handle edge cases like failed backend requests
First, if you're not already using react-toastify (a common library for this pattern), install it:
npm install react-toastify # or yarn add react-toastify
Then import it in your component:
import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css';
Now update the deleteCustomer function with full undo logic:
const deleteCustomer = (ID) => { if (window.confirm("Are you sure you want to delete?")) { // 1. Grab the customer we're about to delete (for undo) const deletedCustomer = customerList.find(val => val.ID === ID); const deletedIndex = customerList.findIndex(val => val.ID === ID); // 2. Update the local list immediately for instant user feedback setCustomerList(prevList => prevList.filter(val => val.ID !== ID)); // 3. Set up an abort controller to cancel the backend request if we undo const abortController = new AbortController(); // 4. Send the delete request to the backend const deleteRequest = Axios.delete(`http://localhost:3001/stats/delete/${ID}`, { signal: abortController.signal }); // 5. Show a toast with an undo button const toastId = toast("Customer deleted successfully!", { autoClose: 5000, // Auto-close after 5 seconds closeOnClick: false, draggable: false, render: ({ closeToast }) => ( <div className="d-flex align-items-center gap-2"> <p className="mb-0">Customer deleted successfully!</p> <button className="btn btn-sm btn-secondary" onClick={() => { // Undo action: restore the customer to its original position setCustomerList(prevList => { const newList = [...prevList]; newList.splice(deletedIndex, 0, deletedCustomer); return newList; }); // Cancel the backend delete request abortController.abort("User clicked undo"); // Close the toast closeToast(); }} > Undo </button> </div> ) }); // 6. Handle backend request results deleteRequest.then(() => { console.log("Customer deleted from backend"); }).catch(error => { // Only restore if the error isn't from us canceling the request if (error.name !== "CanceledError") { setCustomerList(prevList => { const newList = [...prevList]; newList.splice(deletedIndex, 0, deletedCustomer); return newList; }); toast.error("Delete failed—customer restored.", { toastId }); } }); } };
Finally, add the <ToastContainer /> component to your JSX (usually at the root of your component) to render the toasts:
return ( <div className="container"> {/* Your table and other UI elements */} <table className="table"> <thead> {/* Table headers */} </thead> <tbody> {displayCustomers} </tbody> </table> {/* Add this line to render toasts */} <ToastContainer position="bottom-right" /> </div> );
Key Notes:
- We update the local state first so the user sees the delete happen instantly, even before the backend responds.
- The undo button restores the customer to its original position in the list (instead of just appending it) for a better user experience.
- The abort controller prevents unnecessary backend work if the user undoes the action quickly.
- We handle failed backend requests by restoring the customer and showing an error toast.
内容的提问来源于stack exchange,提问作者fyceheist

