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

如何点击按钮触发Ajax请求并填充React Material-UI模态框

实现方案

核心调整点

  • 新增两个状态:modalData 存储接口返回的详情数据,loading 控制模态框加载态
  • 点击按钮时才发起接口请求,请求完成后再打开模态框,完全避免页面加载时批量调用接口的问题
  • 全局只维护一个模态框实例,适配数千行表格的性能要求,不会产生冗余组件
import "./styles.css";
import React from "react";
import { Button, Modal, Typography, Box, CircularProgress } from "@material-ui/core";

export default function App() {
  const [open, setOpen] = React.useState(false);
  const [modalData, setModalData] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const handleClose = () => {
    setOpen(false);
    // 关闭时清空数据,避免下次打开闪现旧数据
    setModalData(null);
  };

  const handleFetchDetail = async (apiUrl) => {
    setLoading(true);
    try {
      const response = await fetch(apiUrl);
      const apiResult = await response.json();
      // 这里根据你的实际接口返回结构调整取值逻辑,示例接口不需要JSON.parse
      setModalData(apiResult);
      setOpen(true);
    } catch (err) {
      console.error("详情获取失败", err);
      // 可根据需求增加错误提示
    } finally {
      setLoading(false);
    }
  };
  
  return (
    <div className="App">
      {/* 表格行内的按钮,实际使用时把每行对应的接口url作为参数传入即可 */}
      <Button
        variant="outlined"
        color="primary"
        onClick={() => handleFetchDetail("https://jsonplaceholder.typicode.com/todos/1")}
      >
        查看详情
      </Button>
      <Modal open={open} onClose={handleClose}>
        <Box sx={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: 400, bgcolor: 'background.paper', border: '2px solid #000', boxShadow: 24, p: 4 }}>
          {loading ? (
            <Box display="flex" justifyContent="center">
              <CircularProgress />
            </Box>
          ) : modalData ? (
            <>
              <Typography variant="h6" component="h2">
                产品详情
              </Typography>
              <Typography sx={{ mt: 2 }}>
                ID: {modalData.id}
              </Typography>
              <Typography sx={{ mt: 1 }}>
                名称: {modalData.title}
              </Typography>
              <Typography sx={{ mt: 1 }}>
                状态: {modalData.completed ? "已完成" : "未完成"}
              </Typography>
            </>
          ) : null}
        </Box>
      </Modal>
    </div>
  );
}

组件拆分适配方案

如果要将行按钮和模态框拆分为独立组件,可按如下逻辑实现:

  1. 封装RowDetailButton组件,接收apiUrl、onDetailFetched两个props,点击按钮发起请求后把返回数据通过回调传给父组件
  2. 封装ProductDetailModal组件,接收open、data、loading、onClose等props,只负责渲染内容,不处理业务逻辑
  3. 父组件统一维护模态框的状态和数据,数千行表格只会渲染一个模态框,性能无损耗
行按钮组件示例
// RowDetailButton.jsx
import React from "react";
import { Button } from "@material-ui/core";

export default function RowDetailButton({ apiUrl, onDetailFetched }) {
  const [loading, setLoading] = React.useState(false);
  const handleClick = async () => {
    setLoading(true);
    try {
      const res = await fetch(apiUrl);
      const data = await res.json();
      onDetailFetched(data);
    } catch (err) {
      console.error("获取详情失败", err);
    } finally {
      setLoading(false);
    }
  };

  return <Button variant="outlined" color="primary" onClick={handleClick} disabled={loading}>查看详情</Button>
}
详情模态框组件示例
// ProductDetailModal.jsx
import React from "react";
import { Modal, Typography, Box, CircularProgress } from "@material-ui/core";

export default function ProductDetailModal({ open, onClose, data, loading }) {
  return (
    <Modal open={open} onClose={onClose}>
      <Box sx={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: 400, bgcolor: 'background.paper', p: 4 }}>
        {loading ? (
          <Box display="flex" justifyContent="center"><CircularProgress /></Box>
        ) : data ? (
          <>
            <Typography variant="h6">产品详情</Typography>
            {/* 按实际字段渲染 */}
            {Object.entries(data).map(([key, value]) => (
              <Typography key={key} sx={{ mt: 1 }}>{key}: {String(value)}</Typography>
            ))}
          </>
        ) : null}
      </Box>
    </Modal>
  )
}
父组件使用示例
// App.jsx
import React from "react";
import RowDetailButton from "./RowDetailButton";
import ProductDetailModal from "./ProductDetailModal";

export default function App() {
  const [modalOpen, setModalOpen] = React.useState(false);
  const [modalData, setModalData] = React.useState(null);
  const [modalLoading, setModalLoading] = React.useState(false);

  const handleDetailFetched = (data) => {
    setModalData(data);
    setModalOpen(true);
  };

  const handleModalClose = () => {
    setModalOpen(false);
    setModalData(null);
  };

  // XGrid列配置示例,行按钮传入对应行的接口地址
  const columns = [
    // 其他列配置省略
    {
      field: 'action',
      headerName: '操作',
      width: 120,
      renderCell: (params) => (
        <RowDetailButton 
          apiUrl={`/your/api/path/${params.row.productId}`} 
          onDetailFetched={handleDetailFetched}
        />
      )
    }
  ];

  return (
    <div>
      {/* 你的XGrid组件渲染,传入columns和行数据即可 */}
      <ProductDetailModal 
        open={modalOpen} 
        onClose={handleModalClose} 
        data={modalData} 
        loading={modalLoading}
      />
    </div>
  )
}

额外优化建议

  • 可根据需求增加接口缓存逻辑,同一行的详情多次点击只发起一次请求,进一步降低服务器压力
  • 点击按钮时增加禁用态,避免用户重复点击发起重复请求
  • 接口报错时可增加全局提示告知用户请求失败

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 17:15:02