You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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处修改即可生效:

  1. 修改openModal方法,接收任务ID参数,点击任务时同时更新两个状态
function openModal(todoId) {
  setIsOpen(true);
  setOpenedId(todoId);
}
  1. 给Task组件的onRequestOpen事件传入当前任务ID
onRequestOpen={() => openModal(todo.id)}
  1. 修改Modal的isOpen判断条件,关闭弹窗时同步清空openedId
function closeModal() {
  setIsOpen(false);
  setOpenedId("");
}

// Modal组件isOpen属性修改
<Modal
  isOpen={modalIsOpen && openedId === todo.id}
  onRequestClose={closeModal}
  // 其余props保持不变
>

这种方案会随任务数量增加渲染多个隐藏的Modal组件,会产生不必要的性能开销,更推荐单全局Modal的实现方式。

内容的提问来源于stack exchange,提问作者Matin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 11:06:19