如何点击按钮触发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> ); }
组件拆分适配方案
如果要将行按钮和模态框拆分为独立组件,可按如下逻辑实现:
- 封装
RowDetailButton组件,接收apiUrl、onDetailFetched两个props,点击按钮发起请求后把返回数据通过回调传给父组件 - 封装
ProductDetailModal组件,接收open、data、loading、onClose等props,只负责渲染内容,不处理业务逻辑 - 父组件统一维护模态框的状态和数据,数千行表格只会渲染一个模态框,性能无损耗
行按钮组件示例
// 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
相关产品推荐
相关产品推荐

