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

Material UI TextField输入中断问题排查求助

问题:Material UI TextField输入字符后失去焦点,需重新点击才能继续输入

问题描述

使用Material UI的TextField组件时,每次输入一个字符后就无法继续输入,必须重新点击输入框才能继续;移除onChange方法后,输入可正常连续进行,怀疑是组件渲染逻辑或useEffect导致问题。

相关代码

DataGridCustomToolbar组件

import React from "react";
import { Search } from "@mui/icons-material";
import { IconButton, TextField, InputAdornment } from "@mui/material";
import {
  GridToolbarDensitySelector,
  GridToolbarContainer,
  GridToolbarExport,
  GridToolbarColumnsButton,
} from "@mui/x-data-grid";
import FlexBetween from "./FlexBetween";

const DataGridCustomToolbar = ({ searchInput, setSearchInput, setSearch }) => {
  return (
    <GridToolbarContainer>
      <FlexBetween width="100%">
        <FlexBetween>
          <GridToolbarColumnsButton />
          {/* <GridToolbarDensitySelector /> */}
          <GridToolbarExport />
        </FlexBetween>
        <TextField
          label="Search..."
          sx={{ mb: "0.5rem", width: "15rem" }}
          onChange={(e) => setSearchInput(e.target.value)}
          value={searchInput}
          variant="standard"
          InputProps={{
            endAdornment: (
              <InputAdornment position="end">
                <IconButton
                  onClick={() => {
                    setSearch({ value: searchInput });
                    setSearchInput("");
                  }}
                >
                  <Search />
                </IconButton>
              </InputAdornment>
            ),
          }}
        />
      </FlexBetween>
    </GridToolbarContainer>
  );
};

export default DataGridCustomToolbar;

BOM组件(DataGrid使用处)

import React, { useState, useEffect, useTheme } from "react";
import { Box } from "@mui/material";
import { StripedDataGrid } from "你的组件路径"; // 替换为实际组件路径
import dataService from "你的服务路径"; // 替换为实际服务路径

const BOM = () => {
  const theme = useTheme();

  // 向后端传递的参数
  const [page, setPage] = useState(0);
  const [limit, setlimit] = useState(20);
  const [order, setOrder] = useState([
    {
      column: "1",
      dir: "asc",
    },
  ]);
  const [search, setSearch] = useState({
    value: "",
  });
  const [data, setData] = useState([]);
  const [total, setTotal] = useState(0);
  const [searchInput, setSearchInput] = useState("");

  useEffect(() => {
    dataService.getBomList(page, limit, order, search).then((response) => {
      console.log("BOM");
      setData(response.data.data.data);
      setTotal(response.data.data.recordsFiltered);
    });
  }, [page, limit, order]);

  const columns = [
    {
      field: "sku",
      headerName: "SKU",
      width: 100,
    },
    { field: "sku_name", headerName: "SKU NAME", width: 300 },
    {
      field: "plant",
      headerName: "Plant",
      width: 80,
      align: "center",
      headerAlign: "center",
    },
    { field: "base_qty", headerName: "Base QTY", width: 100 },
    {
      field: "item",
      headerName: "Item",
      width: 60,
      align: "center",
      headerAlign: "center",
    },
    { field: "uom_sku", headerName: "UOM SKU", width: 100, align: "center" },
    { field: "material", headerName: "Material", width: 150 },
    { field: "material_name", headerName: "Material Name", width: 300 },
    { field: "qty_material", headerName: "Material QTY", width: 100 },
    { field: "uom_material", headerName: "Material UOM", width: 100 },
    { field: "alt_bom", headerName: "BOM ALT", width: 80 },
    { field: "valid_from", headerName: "Valid From", width: 210 },
    { field: "valid_to", headerName: "Valid to", width: 210 },
    { field: "status", headerName: "Status", width: 80 },
  ];
  
  return (
    <Box m="5px">
      <Box height="80vh">
        <StripedDataGrid
          getRowId={(row) => row.id}
          columns={columns}
          rowCount={(limit && total) || 0}
          rowsPerPageOptions={[20, 50, 100]}
          rows={data || []}
          pagination
          paginationMode="server"
          sortingMode="server"
          page={page}
          pageSize={limit}
          onPageSizeChange={(newPageSize) => setlimit(newPageSize)}
          onPageChange={(newPage) => setPage(newPage)}
          onSortModelChange={(newSortModel) => setOrder(...newSortModel)}
          components={{ Toolbar: DataGridCustomToolbar }}
          componentsProps={{
            toolbar: { searchInput, setSearchInput, setSearch },
          }}
        />
      </Box>
    </Box>
  );
};
export default BOM;

错误原因及解决方案

核心错误点

  1. onSortModelChange 写法错误:原代码使用 setOrder(...newSortModel) 将排序数组展开为单个对象,导致 order 从数组变为对象,触发 useEffect 频繁执行、组件反复渲染,最终使TextField失去焦点。
  2. componentsProps.toolbar 频繁生成新对象:每次 searchInput 变化时,BOM 组件重新渲染,componentsProps.toolbar 会生成新的对象引用,导致DataGrid重新挂载Toolbar组件,TextField被销毁重建,失去焦点。
  3. 无用递归函数 setDataInput:该函数会无限递归调用自身,虽未被使用,但存在潜在崩溃风险。
  4. useEffect 依赖缺失:未将 search 加入依赖数组,导致搜索参数变化时无法触发后端数据请求。

修复步骤

  1. 修正排序逻辑:直接将排序数组赋值给 order:
    onSortModelChange={(newSortModel) => setOrder(newSortModel)}
    
  2. 缓存Toolbar props:使用 useMemo 缓存props对象,避免频繁创建新引用:
    import React, { useState, useEffect, useTheme, useMemo } from "react";
    // ...
    componentsProps={{
      toolbar: useMemo(() => ({ searchInput, setSearchInput, setSearch }), [searchInput, setSearchInput, setSearch])
    }}
    
  3. 删除无用递归函数:移除 setDataInput 函数,消除潜在错误。
  4. 补充useEffect依赖:将 search 加入依赖数组,确保搜索参数变化时重新请求数据:
    useEffect(() => {
      dataService.getBomList(page, limit, order, search).then((response) => {
        console.log("BOM");
        setData(response.data.data.data);
        setTotal(response.data.data.recordsFiltered);
      });
    }, [page, limit, order, search]);
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 20:45:23