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

MUI DataGrid新增行时SelectField值未重置的问题求助

MUI DataGrid 添加新行时SelectField保留上一行值的问题

我在使用MUI的DataGrid组件,表格包含id(数字)、processPath(下拉选择框)、associateLogin(文本框)、action(按钮)四列,已经实现了添加/删除行的功能。但现在遇到一个问题:processPath列的下拉框虽然能正常更新值,但每次添加新行时,这个字段会自动带上上一行选中的值。我初步判断问题出在复用的自定义函数组件上,但不知道怎么把新行的这个字段状态重置为空。

原始代码

import {
  FormControl,
  InputLabel,
  MenuItem,
  Select,
  SelectChangeEvent,
  Box,
  Button,
  Stack,
} from "@mui/material";
import {
  DataGrid,
} from "@mui/x-data-grid";
import React, { FunctionComponent, useState } from "react";

const processPaths: string[] = ["Test1", "Test2"];

export const AccomodationsTable: FunctionComponent = () => {
  const [idCounter, setIdCounter] = useState(0);
  const [rowCounter, setRowCounter] = useState(0);

  const [formData, setFormData] = useState([
    {
      id: idCounter,
      processPath: "",
      associateLogin: "",
      action: "",
    },
  ]);

  const ProcessPathSelectField: FunctionComponent = () => {
    function handleChange(event: SelectChangeEvent) {
      const { name, value } = event.target;
      setFormData((prevFormData) => ({
        ...prevFormData,
        [name]: value,
      }));
    }

    return (
      <Box>
        <FormControl fullWidth>
          <InputLabel id={`process-path-select-${idCounter}`}>
            Please Select
          </InputLabel>
          <Select
            labelId="process-path-select"
            id={`process-path-select-${idCounter}`}
            value={formData[0].processPath}
            name="processPath"
            label={`process-path-select-${idCounter}`}
            onChange={handleChange}
            sx={{ minWidth: "240px" }}
          >
            {processPaths.sort().map((processPath) => (
              <MenuItem value={processPath}>{processPath}</MenuItem>
            ))}
          </Select>
        </FormControl>
      </Box>
    );
  };

  const createNewRow = () => {
    setIdCounter((prevIdCounter) => (prevIdCounter + 1));
    return {
      id: idCounter,
      processPath: "",
      associateLogin: "",
      action: "",
    };
  };

  const [rows, setRows] = useState(() => [createNewRow()]);

  const handleAddRow = () => {
    setRows((prevRows) => [...prevRows, createNewRow()]);
  };

  const handleRemoveRow = () => {
    setRows(rows.slice(0, idCounter - 1));
    setIdCounter((prevIdCounter) => (prevIdCounter - 1));
  };

  const columns = [
    { field: "id" },
    {
      field: "Process Path",
      width: 270,
      renderCell: () => <ProcessPathSelectField />,
    },
    { field: "Associate Login", width: 270 },
    { field: "Action", width: 270 },
  ];

  return (
    <>
      <Box sx={{ width: "100%" }}>
        <Stack direction="row" spacing={1} sx={{ mb: 1 }}>
          <Button size="small" onClick={handleRemoveRow}>
            Remove a row
          </Button>
          <Button size="small" onClick={handleAddRow}>
            Add a row
          </Button>
        </Stack>
        <Box sx={{ height: 400, mt: 1 }}>
          <DataGrid
            rows={rows}
            columns={columns}
            columnVisibilityModel={{
              id: false,
            }}
          />
        </Box>
      </Box>
    </>
  );
};

问题根源

  1. 状态冗余且脱节:同时维护formData和rows两个状态,下拉框绑定的是formData[0]的固定值,所有行共享同一个下拉状态。
  2. 自定义组件未关联行数据:ProcessPathSelectField没有接收当前行的id和数据,无法针对单行做独立更新。
  3. id生成逻辑异步问题:调用setIdCounter后立即返回旧的idCounter值,导致行id重复或不准确。

修复后的完整代码

import {
  FormControl,
  InputLabel,
  MenuItem,
  Select,
  SelectChangeEvent,
  Box,
  Button,
  Stack,
} from "@mui/material";
import { DataGrid, GridRenderCellParams } from "@mui/x-data-grid";
import React, { useState } from "react";

const processPaths: string[] = ["Test1", "Test2"];

export const AccomodationsTable = () => {
  // 统一用rows维护所有行数据,删除冗余状态
  const [rows, setRows] = useState([{
    id: 0,
    processPath: "",
    associateLogin: "",
    action: "",
  }]);

  // 针对单行更新processPath值
  const handleProcessPathChange = (id: number, value: string) => {
    setRows(prevRows => 
      prevRows.map(row => 
        row.id === id ? {...row, processPath: value} : row
      )
    );
  };

  // 生成唯一id的新行,确保初始processPath为空
  const createNewRow = () => {
    const maxId = rows.length > 0 ? Math.max(...rows.map(r => r.id)) : 0;
    return {
      id: maxId + 1,
      processPath: "",
      associateLogin: "",
      action: "",
    };
  };

  const handleAddRow = () => {
    setRows(prevRows => [...prevRows, createNewRow()]);
  };

  const handleRemoveRow = () => {
    if (rows.length <= 1) return; // 至少保留一行
    setRows(prevRows => prevRows.slice(0, -1));
  };

  // 下拉框组件接收当前行参数,绑定对应行的processPath
  const ProcessPathSelectField = (params: GridRenderCellParams) => {
    const { row } = params;
    return (
      <Box>
        <FormControl fullWidth>
          <InputLabel id={`process-path-select-${row.id}`}>
            Please Select
          </InputLabel>
          <Select
            labelId={`process-path-select-${row.id}`}
            id={`process-path-select-${row.id}`}
            value={row.processPath}
            label="Please Select"
            onChange={(e) => handleProcessPathChange(row.id, e.target.value as string)}
            sx={{ minWidth: "240px" }}
          >
            {processPaths.sort().map((processPath) => (
              <MenuItem key={processPath} value={processPath}>{processPath}</MenuItem>
            ))}
          </Select>
        </FormControl>
      </Box>
    );
  };

  const columns = [
    { field: "id", hide: true },
    {
      field: "Process Path",
      width: 270,
      renderCell: (params) => <ProcessPathSelectField {...params} />,
    },
    { 
      field: "Associate Login", 
      width: 270,
      // 补充文本框编辑逻辑
      renderCell: (params) => (
        <input 
          type="text" 
          value={params.row.associateLogin}
          onChange={(e) => {
            setRows(prevRows => prevRows.map(row => 
              row.id === params.row.id ? {...row, associateLogin: e.target.value} : row
            ));
          }}
          style={{ width: '100%', padding: '8px', boxSizing: 'border-box' }}
        />
      )
    },
    { 
      field: "Action", 
      width: 270,
      // 补充单行删除逻辑
      renderCell: (params) => (
        <Button 
          size="small" 
          onClick={() => {
            setRows(prevRows => prevRows.filter(row => row.id !== params.row.id));
          }}
        >
          删除此行
        </Button>
      )
    },
  ];

  return (
    <>
      <Box sx={{ width: "100%" }}>
        <Stack direction="row" spacing={1} sx={{ mb: 1 }}>
          <Button size="small" onClick={handleRemoveRow} disabled={rows.length <=1}>
            Remove a row
          </Button>
          <Button size="small" onClick={handleAddRow}>
            Add a row
          </Button>
        </Stack>
        <Box sx={{ height: 400, mt: 1 }}>
          <DataGrid
            rows={rows}
            columns={columns}
          />
        </Box>
      </Box>
    </>
  );
};

关键修复点

  • 合并状态管理:删除formData和idCounter,所有行数据统一存在rows中,确保状态一致。
  • 组件关联行数据:让ProcessPathSelectField接收DataGrid的行参数,每个下拉框绑定对应行的processPath值,实现独立状态。
  • 精准更新逻辑:通过行id定位,只修改当前行的processPath,避免影响其他行。
  • 修复id生成:基于现有行的最大id生成新id,解决异步更新导致的id错误问题。
  • 完善交互:补充文本框编辑、单行删除功能,添加删除行的最小数量限制。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 00:15:05