React使用react-modal点击单项弹多窗、无法关闭问题解决
问题原因
- 初始版本所有弹窗同时弹出:你在列表
map遍历过程中给每个任务项都渲染了一个Modal组件,且所有Modal的显示状态都绑定了同一个modalIsOpen变量,当这个变量被置为true时,所有Modal会同步打开。 - 新增
openedId后弹窗无法关闭:你没有完成状态的全链路联动:点击任务时没有把对应任务ID写入openedId,Modal的isOpen属性没有加入ID匹配判断,关闭弹窗时也没有同步重置openedId状态,导致关闭逻辑失效。另外你把Modal.setAppElement写在了组件渲染函数内部,每次组件重渲染都会重复执行该方法,也可能引发异常。
修复方案
推荐将Modal从列表循环中移出,全局只渲染一个Modal组件,通过openedId匹配当前要展示的任务内容,减少冗余组件渲染,逻辑也更易维护,核心修改后代码如下:
import { useContext, useState, useMemo } from "react"; import Task from "../task/task"; import { todosContext } from "../../context/todosContext"; import { motion, AnimatePresence } from "framer-motion"; import Modal from "react-modal"; // setAppElement移到组件外层,仅执行一次 Modal.setAppElement(document.getElementById("root")); const Search = (props) => { const [query, setQuery] = useState(""); const { todos, setTodos } = useContext(todosContext); // 移除冗余的modalIsOpen状态,直接通过openedId控制弹窗开关 const [openedId, setOpenedId] = useState(""); const PER_PAGE = 4; // 打开弹窗:记录当前点击的任务ID function openModal(todoId) { setOpenedId(todoId); } // 关闭弹窗:清空选中的任务ID function closeModal() { setOpenedId(""); } // 匹配当前选中的任务详情 const currentTodo = useMemo(() => { return todos.find(todo => todo.id === openedId) }, [todos, openedId]) // 保留你原有的分页、删除、状态修改等业务逻辑 const currentPageData = [] const deleteHandler = () => {} const processChange = () => {} const loading = false const customStyles = {} return ( <StyledDiv> <AnimatePresence> {currentPageData.map((todo) => ( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0, position: "absolute", y: -1000, transition: "1s", }} key={todo.id} > <Task // 点击时传入当前任务ID onRequestOpen={() => openModal(todo.id)} key={todo.id} title={todo.title} description={todo.description} backgroundColor="#ffffff" disabled={loading} onClick={() => deleteHandler(todo.id)} placeholder={"completed"} onChange={(e) => processChange(e, todo.id)} checked={todo.completed} id={todo.id} htmlFor={todo.id} marginTop="-5px" ></Task> </motion.div> ))} </AnimatePresence> {/* 循环外仅渲染一个全局Modal */} <Modal isOpen={!!openedId} onRequestClose={closeModal} style={customStyles} contentLabel="任务详情" > <button onClick={closeModal}>close</button> <h3>{currentTodo?.title}</h3> <p>{currentTodo?.description}</p> </Modal> </StyledDiv> ); }; export default Search;
如果你坚持保留循环内渲染多个Modal的写法,仅需做3处修改即可生效:
- 修改
openModal方法,接收任务ID参数,点击任务时同时更新两个状态
function openModal(todoId) { setIsOpen(true); setOpenedId(todoId); }
- 给Task组件的
onRequestOpen事件传入当前任务ID
onRequestOpen={() => openModal(todo.id)}
- 修改Modal的
isOpen判断条件,关闭弹窗时同步清空openedId
function closeModal() { setIsOpen(false); setOpenedId(""); } // Modal组件isOpen属性修改 <Modal isOpen={modalIsOpen && openedId === todo.id} onRequestClose={closeModal} // 其余props保持不变 >
这种方案会随任务数量增加渲染多个隐藏的Modal组件,会产生不必要的性能开销,更推荐单全局Modal的实现方式。
内容的提问来源于stack exchange,提问作者Matin
相关产品推荐
相关产品推荐

