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

React DataGrid增改记录问题:getActions未获取id致按钮不切换

React DataGrid中getActions无法获取id,编辑按钮无法切换状态问题

我跟着React教程实现基于DataGrid的CRUD数据操作功能,但遇到了问题:代码中的getActions方法无法正确获取行id对应的编辑状态,导致isInEditMode始终为false,点击编辑按钮后无法切换为保存和取消按钮。

相关代码

AdminView组件代码

import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { Box, Typography } from '@mui/material';
import Button from '@mui/material/Button';
import AddIcon from '@mui/icons-material/Add';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Close';
import EditToolbar from './EditToolbar';

import {
  GridRowModes,
  DataGrid,
  GridToolbarContainer,
  GridActionsCellItem,
  GridRowEditStopReasons,
} from '@mui/x-data-grid';

const AdminView = ()=> {
  const [rows, setRows] = React.useState([]);
  const [rowModesModel, setRowModesModel] = React.useState({});

  useEffect(() => {
    axios.get('http://localhost:3001/users')
      .then(response => {
        setRows(response.data);
      })
      .catch(error => {
        console.error('Error fetching user data:', error);
      });
  }, []);
  
  const [columns, setColumns] = useState([
    { field: "id"},
    { field: "staff_number", editable: true },
    { field: "name", editable: true },
    { field: "surname", editable: true },
    { field: "email", editable: true  },
    { field: "password", editable: true },
    { field: "access_level", editable: true },
    {
      field: 'actions',
      type: 'actions',
      headerName: 'Actions',
      width: 100,
      cellClassName: 'actions',
      getActions: ({ id }) => {
        console.log('get action:', rowModesModel[id]);
        const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;

        if (isInEditMode) {
          return [
            <GridActionsCellItem
              icon={<SaveIcon />}
              label="Save"
              sx={{ color: 'primary.main' }}
              onClick={handleSaveClick(id)}
            />,
            <GridActionsCellItem
              icon={<CancelIcon />}
              label="Cancel"
              className="textPrimary"
              onClick={handleCancelClick(id)}
              color="inherit"
            />,
          ];
        }

        return [
          <GridActionsCellItem
            icon={<EditIcon />}
            label="Edit"
            className="textPrimary"
            onClick={handleEditClick(id)}
            color="inherit"
          />,
          <GridActionsCellItem
            icon={<DeleteIcon />}
            label="Delete"
            onClick={handleDeleteClick(id)}
            color="inherit"
          />,
        ];
      },
    }
  ]); 

  const handleEditClick = (id) => () => {
    setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } });
  };
  const handleSaveClick = (id) => () => {
    console.log('handleSaveClick:', id);
    setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } });
  };
  const handleDeleteClick = (id) => () => {
    setRows(rows.filter((row) => row.id !== id));
  };

  const handleCancelClick = (id) => () => {
    setRowModesModel({
      ...rowModesModel,
      [id]: { mode: GridRowModes.View, ignoreModifications: true },
    });

    const editedRow = rows.find((row) => row.id === id);
    if (editedRow.isNew) {
      setRows(rows.filter((row) => row.id !== id));
    }
  };

  const handleModeRowsChange = (newRowModesModel ) => {
    console.log('newRowModesModel change to:', newRowModesModel);
    setRowModesModel(newRowModesModel);
  }
  const handleRowEditStop = (params, event) => {
    console.log('handleRowEditStop params:', params);
    if (params.reason === GridRowEditStopReasons.rowFocusOut) {
      event.defaultMuiPrevented = true;
    }
  };

  const processRowUpdate = (newRow) => {
    console.log('processRowUpdate newRow returned:', newRow);
    const updatedRow = { ...newRow, isNew: false };
    setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
    console.log('updatedRow returned:', updatedRow);
    return updatedRow;
  };

  return (
    <div>
      <DataGrid
        rows={rows}
        columns={columns}
        editMode="row"
        checkboxSelection
        rowModesModel={rowModesModel}
        onRowModesModelChange={handleModeRowsChange}
        onRowEditStop={handleRowEditStop}
        processRowUpdate={processRowUpdate}
        slots={{ toolbar: EditToolbar }}
        slotProps={{ toolbar: { setRows, setRowModesModel } }}
      />
    </div>
  );
};

export default AdminView;

EditToolbar组件代码

import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import AddIcon from '@mui/icons-material/Add';
import { GridToolbarContainer } from '@mui/x-data-grid';
import { randomId } from '@mui/x-data-grid-generator';
import { GridRowModes } from '@mui/x-data-grid';

function EditToolbar(props) {
  const { setRows, setRowModesModel } = props;

  const handleClick = () => {
    const id = randomId();
    setRows((oldRows) => [
      ...oldRows,
      {
        id,
        name: '',
        surname: '',
        email: '',
        password: '',
        access_level: '',
        staff_number: '',
        isNew: true
      }
    ]);
  
    setRowModesModel((oldModel) => ({
      ...oldModel,
      [id]: { mode: GridRowModes.Edit, fieldToFocus: 'staff_number' }
    }));
  };

  return (
    <GridToolbarContainer>
      <Button color="primary" startIcon={<AddIcon />} onClick={handleClick}>
        Add record
      </Button>
    </GridToolbarContainer>
  );
}

export default EditToolbar;

问题原因及修复方案

1. 核心问题:Columns使用useState导致状态不更新

你把columns放在useState中,但没有添加任何依赖项。这意味着组件渲染时,columns只会初始化一次,后续rowModesModel变化时,getActions方法中的rowModesModel始终是初始的空对象,无法正确判断行的编辑状态。

修复:
把columns从useState改成普通常量,去掉状态包裹:

// 替换原来的const [columns, setColumns] = useState([...])
const columns = [
  { field: "id"},
  { field: "staff_number", editable: true },
  { field: "name", editable: true },
  { field: "surname", editable: true },
  { field: "email", editable: true  },
  { field: "password", editable: true },
  { field: "access_level", editable: true },
  {
    field: 'actions',
    type: 'actions',
    headerName: 'Actions',
    width: 100,
    cellClassName: 'actions',
    getActions: ({ id }) => {
      console.log('get action:', rowModesModel[id]);
      const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;

      if (isInEditMode) {
        return [
          <GridActionsCellItem
            icon={<SaveIcon />}
            label="Save"
            sx={{ color: 'primary.main' }}
            onClick={handleSaveClick(id)}
          />,
          <GridActionsCellItem
            icon={<CancelIcon />}
            label="Cancel"
            className="textPrimary"
            onClick={handleCancelClick(id)}
            color="inherit"
          />,
        ];
      }

      return [
        <GridActionsCellItem
          icon={<EditIcon />}
          label="Edit"
          className="textPrimary"
          onClick={handleEditClick(id)}
          color="inherit"
        />,
        <GridActionsCellItem
          icon={<DeleteIcon />}
          label="Delete"
          onClick={handleDeleteClick(id)}
          color="inherit"
        />,
      ];
    },
  }
];

2. 可选优化:确保ID类型一致

如果后端返回的id是数字类型,而新增行的id是字符串,可能导致匹配失败。可以在获取数据时统一转换为字符串:

useEffect(() => {
  axios.get('http://localhost:3001/users')
    .then(response => {
      // 将id转换为字符串,保持类型一致
      const formattedRows = response.data.map(row => ({ ...row, id: String(row.id) }));
      setRows(formattedRows);
    })
    .catch(error => {
      console.error('Error fetching user data:', error);
    });
}, []);

3. 可选优化:使用函数式更新避免闭包问题

在更新状态时,使用函数式更新获取最新的状态值,避免闭包导致的旧状态问题:

const handleEditClick = (id) => () => {
  setRowModesModel(prevModel => ({ ...prevModel, [id]: { mode: GridRowModes.Edit } }));
};

const handleSaveClick = (id) => () => {
  setRowModesModel(prevModel => ({ ...prevModel, [id]: { mode: GridRowModes.View } }));
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 11:43:10