React结合ContextAPI与Reducer实现删除确认Modal的问题
Hey there! Let's break down why your confirmation modal is deleting the wrong item and how to fix it, plus some tips to clean up the code.
Why This Is Happening
The core issue here is shared state across list items combined with React's rendering behavior. If you're using a single currentDeleteId or showModal state in the parent component for all list items, when you loop through your array with Array.prototype.map(), the last item's ID ends up overriding the state value before the modal opens. This happens because React batches state updates, and by the time the modal renders, the state has been set to the last item's ID from the loop.
Another possible culprit is closure issues in your click handlers. If your delete button's onClick callback references a state variable that gets updated in the loop, the callback might capture the final value of that variable instead of the one corresponding to the clicked item.
Step-by-Step Solution
Let's go through two solid fixes depending on your setup:
1. Split List Items into a Reusable Component (Cleanest Approach)
By moving each list item into its own component, you give each item its own isolated state for the modal. This eliminates cross-item state interference entirely.
// ListItem.jsx import { useState, useContext } from 'react'; import { AppContext } from './AppContext'; export const ListItem = ({ item }) => { const [showModal, setShowModal] = useState(false); const { dispatch } = useContext(AppContext); const handleDeleteConfirm = () => { dispatch({ type: 'DELETE_ITEM', payload: item.id }); setShowModal(false); }; return ( <div className="list-item"> <span>{item.text}</span> <button onClick={() => setShowModal(true)}>Remove</button> {showModal && ( <div className="modal"> <p>Are you sure you want to delete this item?</p> <button onClick={handleDeleteConfirm}>Delete</button> <button onClick={() => setShowModal(false)}>Cancel</button> </div> )} </div> ); };
Then in your parent component, render the list like this:
// ParentComponent.jsx import { ListItem } from './ListItem'; // ... {state.items.map(item => ( <ListItem key={item.id} item={item} /> ))}
2. Pass the Item ID Directly to Modal Confirmation (If You Want a Shared Modal)
If you prefer using a single global modal instead of one per item, make sure to pass the correct item ID when opening the modal, and store that ID in state immediately. Avoid relying on loop-scoped variables that can be overwritten.
// ParentComponent.jsx import { useState, useContext } from 'react'; import { AppContext } from './AppContext'; export const ParentComponent = () => { const { state, dispatch } = useContext(AppContext); const [showModal, setShowModal] = useState(false); const [itemToDeleteId, setItemToDeleteId] = useState(null); const handleOpenModal = (itemId) => { setItemToDeleteId(itemId); setShowModal(true); }; const handleDeleteConfirm = () => { dispatch({ type: 'DELETE_ITEM', payload: itemToDeleteId }); setShowModal(false); setItemToDeleteId(null); }; return ( <div> {state.items.map(item => ( <div key={item.id} className="list-item"> <span>{item.text}</span> <button onClick={() => handleOpenModal(item.id)}>Remove</button> </div> ))} {showModal && ( <div className="modal"> <p>Are you sure you want to delete this item?</p> <button onClick={handleDeleteConfirm}>Delete</button> <button onClick={() => setShowModal(false)}>Cancel</button> </div> )} </div> ); };
Optimization Tips
- Use
useCallbackfor Dispatch Handlers: If your parent component re-renders often, wrap your delete handler inuseCallbackto prevent unnecessary re-renders of list items:import { useCallback } from 'react'; const handleDeleteConfirm = useCallback(() => { dispatch({ type: 'DELETE_ITEM', payload: itemToDeleteId }); setShowModal(false); setItemToDeleteId(null); }, [dispatch, itemToDeleteId]); - Add Unique Keys: Always ensure your
map()items have a uniquekeyprop (usingitem.idis perfect here) to help React track list items correctly. - Encapsulate Modal Logic: Create a custom hook like
useConfirmationModalto reuse the confirm/cancel logic across your app, so you don't repeat modal state code everywhere:
Then use it in your component:// useConfirmationModal.js import { useState } from 'react'; export const useConfirmationModal = () => { const [showModal, setShowModal] = useState(false); const [action, setAction] = useState(null); const openModal = (confirmAction) => { setAction(confirmAction); setShowModal(true); }; const confirm = () => { action?.(); setShowModal(false); setAction(null); }; const cancel = () => { setShowModal(false); setAction(null); }; return { showModal, openModal, confirm, cancel }; };const { showModal, openModal, confirm, cancel } = useConfirmationModal(); // When clicking remove: <button onClick={() => openModal(() => dispatch({ type: 'DELETE_ITEM', payload: item.id }))}>Remove</button> // Modal: {showModal && ( <div className="modal"> <p>Are you sure?</p> <button onClick={confirm}>Delete</button> <button onClick={cancel}>Cancel</button> </div> )}
内容的提问来源于stack exchange,提问作者sirrus

