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

Next.js中Material UI选择框值更新异常问题求助

动态生成的Material UI选择框值无法即时更新问题

我在Next.js中使用Material UI构建一个表单,该表单可根据用户输入动态生成不同数量的选择框对。但当前遇到问题:修改选择框内的值时,显示文本不会即时更新,仅在添加新选择框对时才更新。

我尝试过多种方案但均存在问题:

  • 使用defaultValue替代value虽解决显示问题,但控制台会抛出非受控输入警告
  • 手动赋值在新增选择框时会出现异常
  • 移除添加按钮后完全无法更新
  • 尝试移除再添加选择框的方式效果不稳定

原代码

import {
  ChangeEventHandler,
  ReactElement,
  useContext,
  useEffect,
  useState,
} from "react";
import { NextPageWithLayout } from "../../../utils/types";
import styles from "./index.module.scss";
import * as React from "react";
import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { ThemeProvider, createTheme } from "@mui/material/styles";

const DashboardPage: NextPageWithLayout = () => {

  const [users, setUsers] = React.useState(["none"]);
  const [tzValues, setTzs] = React.useState(["none"]);
  const [selects, changeSelects] = useState(1);

  const handleChangeUser = (event: SelectChangeEvent, i:number) => {
    var tempUsers = users;
    if (tempUsers[i] == undefined) {tempUsers.push(event.target.value)}
    else {tempUsers[i] = event.target.value}
    setUsers(tempUsers);
    console.log(users)
  };

  const handleChangeTz = (event: SelectChangeEvent, i:number) => {
    var tempTz = tzValues;
    if (tempTz[i] == undefined) {tempTz.push(event.target.value)}
    else {tempTz[i] = event.target.value}
    setTzs(tempTz);
    console.log(tzValues)
  };

  const submit = (event: any) => {
    // irrelevant to the question
  };

  var options = ["test1", "test2", "test3"];

  const darkTheme = createTheme({
    palette: {
      mode: "dark",
    },
  });

  const addSelect = function () {
    let tempTz = tzValues
    tempTz.push("");
    setTzs(tempTz);
    let tempUsers = users
    tempUsers.push("none")
    setUsers(tempUsers);
    changeSelects(selects + 1);
  };

  return (
    <div className="page">
      <form onSubmit={submit} className={styles.stuff}>
        <div className={styles.times} id="times" style={{ display: "none" }}>
          <ThemeProvider theme={darkTheme}>
            {[...Array(selects)].map((e, i) => (
              <div key={i} className={styles.selectContainer}>
                <FormControl sx={{ m: 1, minWidth: 80 }}>
                  <Select
                    id={"selectUser"+i}
                    defaultValue={users[i] || "none"}
                    onChange={(ev) => handleChangeUser(ev, i)}
                    autoWidth
                    MenuProps={{ PaperProps: { sx: { maxHeight: 200 } } }}
                  >
                    <MenuItem value="none" selected>
                      <em>None</em>
                    </MenuItem>
                    {options.map((option) => (
                      <MenuItem value={option} key={Math.random()}>
                        {option}
                      </MenuItem>
                    ))}
                  </Select>
                </FormControl>
                <FormControl sx={{ m: 1, minWidth: 80 }}>
                  <Select
                    id={"selectZone"+i}
                    defaultValue={tzValues[i] || "none"}
                    onChange={(ev)=>handleChangeTz(ev,i)}
                    autoWidth
                    MenuProps={{ PaperProps: { sx: { maxHeight: 200 } } }}
                  >
                    <MenuItem value="none" selected>
                      <em>None</em>
                    </MenuItem>
                    {["-12","-11","-10","-9","-8","-7","-6","-5","-4","-3","-2","-1","+0","+1","+2","+3","+4","+5","+6","+7","+8","+9","+10","+11","+12",].map((option) => (
                      <MenuItem value={option} key={Math.random()}>
                        GMT{option}
                      </MenuItem>
                    ))}
                  </Select>
                </FormControl>
                <button type="button" className={styles.addtz} onClick={addSelect}>
                  +
                </button>
                <br />
              </div>
            ))}
          </ThemeProvider>
        </div>
        <div>
          <button className={styles.save}>Save</button>
        </div>
      </form>
    </div>
  );
};

export default DashboardPage;

问题根源

  1. 直接修改原状态数组:React状态是不可变的,直接修改users、tzValues原数组不会改变其引用,React无法检测到状态变化,因此不会触发组件重新渲染。
  2. 使用defaultValue而非value:defaultValue仅在组件初始化时生效,后续状态更新无法同步到选择框,导致显示内容不更新。
  3. 随机key引发异常:MenuItem使用Math.random()作为key,会导致组件频繁销毁重建,引发不可预期的渲染问题。

修复后的代码

import {
  ReactElement,
  useState,
} from "react";
import { NextPageWithLayout } from "../../../utils/types";
import styles from "./index.module.scss";
import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { ThemeProvider, createTheme } from "@mui/material/styles";

const DashboardPage: NextPageWithLayout = () => {
  const [users, setUsers] = useState(["none"]);
  const [tzValues, setTzs] = useState(["none"]);
  const [selects, changeSelects] = useState(1);

  const handleChangeUser = (event: SelectChangeEvent, i: number) => {
    // 创建原数组的浅拷贝,避免直接修改原状态
    const tempUsers = [...users];
    tempUsers[i] = event.target.value;
    setUsers(tempUsers);
  };

  const handleChangeTz = (event: SelectChangeEvent, i: number) => {
    const tempTz = [...tzValues];
    tempTz[i] = event.target.value;
    setTzs(tempTz);
  };

  const submit = (event: any) => {
    event.preventDefault();
    // 提交逻辑
  };

  const options = ["test1", "test2", "test3"];
  const timeZones = ["-12","-11","-10","-9","-8","-7","-6","-5","-4","-3","-2","-1","+0","+1","+2","+3","+4","+5","+6","+7","+8","+9","+10","+11","+12"];

  const darkTheme = createTheme({
    palette: {
      mode: "dark",
    },
  });

  const addSelect = () => {
    // 新增时直接更新数组,保持状态一致
    setUsers(prev => [...prev, "none"]);
    setTzs(prev => [...prev, "none"]);
    changeSelects(prev => prev + 1);
  };

  return (
    <div className="page">
      <form onSubmit={submit} className={styles.stuff}>
        <div className={styles.times} id="times">
          <ThemeProvider theme={darkTheme}>
            {[...Array(selects)].map((_, i) => (
              <div key={i} className={styles.selectContainer}>
                <FormControl sx={{ m: 1, minWidth: 80 }}>
                  <Select
                    id={`selectUser${i}`}
                    // 使用value绑定状态,实现受控组件
                    value={users[i] || "none"}
                    onChange={(ev) => handleChangeUser(ev, i)}
                    autoWidth
                    MenuProps={{ PaperProps: { sx: { maxHeight: 200 } } }}
                  >
                    <MenuItem value="none">
                      <em>None</em>
                    </MenuItem>
                    {options.map((option) => (
                      // 使用稳定的key值
                      <MenuItem value={option} key={option}>
                        {option}
                      </MenuItem>
                    ))}
                  </Select>
                </FormControl>
                <FormControl sx={{ m: 1, minWidth: 80 }}>
                  <Select
                    id={`selectZone${i}`}
                    value={tzValues[i] || "none"}
                    onChange={(ev) => handleChangeTz(ev, i)}
                    autoWidth
                    MenuProps={{ PaperProps: { sx: { maxHeight: 200 } } }}
                  >
                    <MenuItem value="none">
                      <em>None</em>
                    </MenuItem>
                    {timeZones.map((option) => (
                      <MenuItem value={option} key={option}>
                        GMT{option}
                      </MenuItem>
                    ))}
                  </Select>
                </FormControl>
                <button type="button" className={styles.addtz} onClick={addSelect}>
                  +
                </button>
                <br />
              </div>
            ))}
          </ThemeProvider>
        </div>
        <div>
          <button className={styles.save}>Save</button>
        </div>
      </form>
    </div>
  );
};

export default DashboardPage;

修复关键点

  • 不可变状态更新:修改状态时创建数组的浅拷贝([...users])或使用函数式更新(setUsers(prev => [...prev, ...])),确保状态引用变化触发React重新渲染。
  • 受控组件绑定:将Select组件的defaultValue替换为value,绑定对应状态变量,实现状态与UI的双向同步,消除非受控输入警告。
  • 稳定key值:将MenuItem的key改为选项本身(或固定索引),避免随机key导致的组件异常重建。
  • 简化状态更新逻辑:新增选择框时使用函数式更新获取最新状态,确保状态修改的原子性和一致性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 23:00:58