React MUI DataGrid单行列图标更新异常求助
问题:MUI DataGrid下载按钮加载状态全局生效,而非仅目标行
我正在开发一款SFTP文件上传工具,可与S3进行文件上传/下载,使用MUI DataGrid组件。点击下载/上传按钮能正常操作对应行的文件,但点击下载后,所有行的下载图标都变成加载图标,而不是只有点击的那一行。我已经用了useCallback和useMemo,但问题依然存在,求指出问题所在。
原ListFiles组件代码
import { FileDownload } from "@mui/icons-material"; import { CssBaseline, IconButton } from "@mui/material"; import { DataGrid } from "@mui/x-data-grid"; import { toast } from "react-toastify"; import React, { useCallback, useEffect, useState } from "react"; import type { FileDetailsType, ListFilesProps } from "../../Types/Types"; import { downloadFile } from "../../api/fileAPI"; import Spinner from "../../utils/Spinner/Spinner"; import { errorToastOptions, successToastOptions } from "../../utils/Toast/ToastOptions"; const ListFiles = ({ files }: ListFilesProps) => { const [fileDownloading, setFileDownloading] = useState(false); const [rows, setRows] = useState([]); useEffect(() => { setRows( files?.map((file) => { return { name: file.name, lastAccessedTime: file.lastAccessedTime, lastModifiedTime: file.lastModifiedTime }; }) ); }, [files]); const handleFileDownload = React.useCallback( async (fileData: FileDetailsType) => { try { setFileDownloading(true); const response = await downloadFile(fileData); if (response) { const blob = new Blob([ response.data as unknown as BlobPart ]); const downloadURL = URL.createObjectURL(blob); const tempLink = document.createElement("a"); tempLink.href = downloadURL; tempLink.download = fileData?.name; tempLink.click(); toast.success( "File Download Successfully", successToastOptions ); setFileDownloading(false); } else { toast.error("File Download Failed", errorToastOptions); setFileDownloading(false); } } catch (error) { toast.error("File Download Failed", errorToastOptions); setFileDownloading(false); } }, [] ); const renderRowBtn = useCallback( (params) => { return ( <IconButton onClick={() => handleFileDownload(params.row)}> {fileDownloading ? <Spinner /> : <FileDownload />} </IconButton> ); }, [] ); const columns = React.useMemo( () => [ { field: "name", headerName: "Name", flex: 2 }, { field: "lastAccessedTime", headerName: "Last Accessed", flex: 1 }, { field: "lastModifiedTime", headerName: "Last Modified", flex: 1 }, { field: "type", headerName: "", flex: 1, renderCell: renderRowBtn } ], [] ); return ( <React.Fragment> <CssBaseline /> <div style={{ width: "100%" }}> <DataGrid columns={columns} rows={rows} getRowId={(row) => row.name} /> </div> </React.Fragment> ); }; export default ListFiles;
原fileAPI代码
import axios from "axios"; import type { FileDetailsType } from "../Types/Types"; export const checkIsConnected = async () => { return await axios.get(`${process.env.API_URL}/api/isconnected`); }; export const listFiles = async () => { return await axios.get( `${process.env.API_URL}/api/listchildnodes?folderPath=/` ); }; export const downloadFile = async (fileData: FileDetailsType) => { return await axios.get( `${process.env.API_URL}/api/download?filePath=${fileData?.name}`, { responseType: "arraybuffer" } ); }; export const uploadFile = async (formData, xsrfToken) => { return await axios.post(`${process.env.API_URL}/api/upload`, formData, { headers: { "X-XSRF-TOKEN": xsrfToken } }); };
问题根源
你用了一个全局布尔值fileDownloading控制所有行的加载状态,一旦设为true,所有行的按钮都会切换成加载图标。同时,renderRowBtn和columns的依赖数组不完整,导致组件无法感知状态变化,状态更新后按钮也不会重新渲染。
修改方案
- 将
fileDownloading改为存储当前正在下载的文件名(唯一标识),精准定位目标行。 - 更新
handleFileDownload,开始下载时记录文件名,结束时清空。 - 补全
renderRowBtn和columns的依赖数组,确保状态变化时触发重渲染。
修改后的ListFiles组件代码
import { FileDownload } from "@mui/icons-material"; import { CssBaseline, IconButton } from "@mui/material"; import { DataGrid } from "@mui/x-data-grid"; import { toast } from "react-toastify"; import React, { useCallback, useEffect, useState } from "react"; import type { FileDetailsType, ListFilesProps } from "../../Types/Types"; import { downloadFile } from "../../api/fileAPI"; import Spinner from "../../utils/Spinner/Spinner"; import { errorToastOptions, successToastOptions } from "../../utils/Toast/ToastOptions"; const ListFiles = ({ files }: ListFilesProps) => { // 改为存储正在下载的文件名,初始值为null const [downloadingFileName, setDownloadingFileName] = useState<string | null>(null); const [rows, setRows] = useState([]); useEffect(() => { setRows( files?.map((file) => { return { name: file.name, lastAccessedTime: file.lastAccessedTime, lastModifiedTime: file.lastModifiedTime }; }) ); }, [files]); const handleFileDownload = useCallback( async (fileData: FileDetailsType) => { try { // 记录当前正在下载的文件名 setDownloadingFileName(fileData.name); const response = await downloadFile(fileData); if (response) { const blob = new Blob([ response.data as unknown as BlobPart ]); const downloadURL = URL.createObjectURL(blob); const tempLink = document.createElement("a"); tempLink.href = downloadURL; tempLink.download = fileData?.name; tempLink.click(); toast.success( "文件下载成功", successToastOptions ); } else { toast.error("文件下载失败", errorToastOptions); } } catch (error) { toast.error("文件下载失败", errorToastOptions); } finally { // 无论成功失败,都清空正在下载的文件名 setDownloadingFileName(null); } }, [] ); const renderRowBtn = useCallback( (params) => { // 判断当前行的文件名是否等于正在下载的文件名 const isDownloading = params.row.name === downloadingFileName; return ( <IconButton onClick={() => handleFileDownload(params.row)}> {isDownloading ? <Spinner /> : <FileDownload />} </IconButton> ); }, // 加入依赖,确保状态变化时重新渲染按钮 [downloadingFileName, handleFileDownload] ); const columns = useMemo( () => [ { field: "name", headerName: "文件名", flex: 2 }, { field: "lastAccessedTime", headerName: "最后访问时间", flex: 1 }, { field: "lastModifiedTime", headerName: "最后修改时间", flex: 1 }, { field: "actions", headerName: "操作", flex: 1, renderCell: renderRowBtn } ], // columns依赖renderRowBtn,加入到依赖数组 [renderRowBtn] ); return ( <React.Fragment> <CssBaseline /> <div style={{ width: "100%" }}> <DataGrid columns={columns} rows={rows} getRowId={(row) => row.name} /> </div> </React.Fragment> ); }; export default ListFiles;
关键修改说明
- 状态精准化:从全局布尔值改为存储具体文件名,实现单一行的加载状态控制。
- 可靠的状态重置:用
finally块确保无论请求成功或失败,都能重置下载状态,避免永久加载。 - 完整的依赖数组:补全
renderRowBtn和columns的依赖,保证React能正确触发重渲染,更新按钮状态。
内容的提问来源于stack exchange,提问作者Markus
相关产品推荐
相关产品推荐

