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

快速挂载卸载Material UI DataGrid报错:无法更新已卸载组件状态

React中Material UI DataGrid快速挂载卸载的状态更新警告解决办法

我在React应用中使用Material UI的DataGrid组件时,遇到了一个问题:当快速挂载、卸载该组件(比如快速切换标签页或通过React Router DOM导航),控制台会抛出警告:Warning: Can't perform a React state update on an unmounted component.。组件本身渲染正常,我没有在组件内执行异步操作,仅通过传入新数据更新状态。已经使用了最新版本的@mui/datagrid和@mui/material,尝试过用挂载状态判断跳过setState调用、useRef存储数据+useState强制更新等方案,但都没解决,怀疑是DataGrid组件内部的问题。

原组件代码

import {
  Button,
  Grid,
  Stack,
  Tooltip,
  Typography,
  useTheme,
} from '@mui/material';
import { DataGrid } from '@mui/x-data-grid';
import React from 'react';

import AssignmentTurnedInIcon from '@mui/icons-material/AssignmentTurnedIn';
import { CustomDatagridPagination } from 'app/components/CustomDatagridPagination';

export const TemplatesTable = ({ rows, onOpenAssignationsModal }) => {
  const theme = useTheme();

  const templateColumns = [
    {
      field: 'name',
      headerName: 'Plantilla',
      flex: 1,
      renderCell: ({ row }) => {
        return (
          <Grid
            container
            sx={{ display: 'flex', flexDirection: 'column', marginY: 2 }}
          >
            <Typography marginBottom={1} fontWeight={'bold'}>
              {row.name}
            </Typography>
            <Typography sx={{ color: theme.palette.grey[600] }}>
              {row.description}
            </Typography>
          </Grid>
        );
      },
    },
    {
      field: 'questions',
      headerName: 'Preguntas',
      valueGetter: ({ value }) => {
        if (value) {
          return value.items.length;
        } else {
          return 0;
        }
      },
    },
    {
      field: 'assignments',
      headerName: 'Asignaciones',
      valueGetter: ({ value }) => {
        if (value) {
          return value.length;
        } else {
          return 0;
        }
      },
    },
    {
      field: 'actions',
      headerName: 'Acciones',
      disableColumnMenu: true,
      sortable: false,
      filtrable: false,
      renderCell: ({ row }) => {
        return (
          <Grid container>
            <Tooltip title='Ver asignaciones' arrow>
              <Button onClick={() => onOpenAssignationsModal(row)}>
                <AssignmentTurnedInIcon />
              </Button>
            </Tooltip>
          </Grid>
        );
      },
    },
  ];

  return (
    <React.Fragment>
      <DataGrid
        columns={templateColumns}
        rows={rows || []}
        rowSelection={false}
        sx={{
          '&.MuiDataGrid-root .MuiDataGrid-cell:focus-within': {
            outline: 'none !important',
          },
          '& .MuiDataGrid-virtualScroller::-webkit-scrollbar': {
            width: '0.4em',
          },
          '& .MuiDataGrid-virtualScroller::-webkit-scrollbar-track': {
            background: '#f1f1f1',
          },
          '& .MuiDataGrid-virtualScroller::-webkit-scrollbar-thumb': {
            backgroundColor: '#999',
          },
          '& .MuiDataGrid-virtualScroller::-webkit-scrollbar-thumb:hover': {
            background: '#555',
          },
        }}
        pageSizeOptions={[5, 10, 25]}
        getRowHeight={() => 'auto'}
        disableRowSelectionOnClick
        initialState={{
          pagination: { paginationModel: { pageSize: 5 } },
        }}
        slots={{
          pagination: CustomDatagridPagination,
        }}
        components={{
          NoRowsOverlay: () => (
            <Stack height='100%' alignItems='center' justifyContent='center'>
              No se encontraron plantillas
            </Stack>
          ),
        }}
      />
    </React.Fragment>
  );
};

尝试过的无效解决方案

// 省略重复代码部分
  const dataRef = useRef([]);

  const [forceUpdate, setForceUpdate] = useState(false);

  const [isMounted, setIsMounted] = useState(false);

  useEffect(() => {
    dataRef.current = rows; // Actualiza la referencia con los nuevos datos

    setIsMounted(true);

    if (isMounted) {
      setForceUpdate((prev) => !prev);
    }

    return () => {
      setIsMounted(false);
    };
  }, [rows]);

  return (
    <React.Fragment key={forceUpdate}>
      <DataGrid
        columns={templateColumns}
        rows={dataRef.current}
        rowSelection={false}
// 省略后续重复代码

可行解决方案

方案1:通过key属性强制DataGrid重建

当组件快速卸载挂载时,给DataGrid设置一个随挂载状态变化的key,强制组件完全重建,避免内部残留的状态更新逻辑触发警告:

export const TemplatesTable = ({ rows, onOpenAssignationsModal }) => {
  // ... 保留原有的theme、templateColumns等代码

  // 使用时间戳作为key,确保每次挂载时DataGrid都是新实例
  const gridKey = useRef(Date.now());
  
  useEffect(() => {
    gridKey.current = Date.now();
  }, []);

  return (
    <DataGrid
      key={gridKey.current}
      columns={templateColumns}
      rows={rows || []}
      // ... 保留原有的其他props
    />
  );
};

方案2:利用AbortController清理内部异步操作

DataGrid内部可能存在未被处理的异步更新,通过AbortController在组件卸载时终止这些操作:

export const TemplatesTable = ({ rows, onOpenAssignationsModal }) => {
  // ... 保留原有的theme、templateColumns等代码

  const abortController = useRef(new AbortController());

  useEffect(() => {
    const controller = abortController.current;
    return () => {
      controller.abort();
    };
  }, []);

  return (
    <DataGrid
      columns={templateColumns}
      rows={rows || []}
      // ... 保留原有的其他props
    />
  );
};

方案3:调整MUI版本

如果确认是DataGrid的内部bug,可以尝试降级到已知稳定的版本(比如@mui/x-data-grid@6.18.0),或者关注MUI官方issue追踪修复进度,待修复后升级到最新版本。

方案4:用React.memo优化渲染并配合清理逻辑

将组件包裹在React.memo中减少不必要的重渲染,同时确保组件卸载时清理相关状态:

export const TemplatesTable = React.memo(({ rows, onOpenAssignationsModal }) => {
  // ... 原组件代码

  useEffect(() => {
    return () => {
      // 组件卸载时的清理逻辑
    };
  }, []);

  return (
    <DataGrid
      key={JSON.stringify(rows)} // 当rows内容变化时强制重建
      // ... 原props
    />
  );
});

为什么之前的方案无效

你尝试的isMounted判断方案无效,是因为setIsMounted(true)是异步更新,在同一次useEffect执行中isMounted仍然是初始的false,导致setForceUpdate不会触发。而useRef存储数据的方式无法解决DataGrid内部的状态更新问题——组件卸载后,DataGrid内部的钩子依然可能在执行状态更新,从而抛出警告。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 08:27:12