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;
问题根源
- 直接修改原状态数组:React状态是不可变的,直接修改
users、tzValues原数组不会改变其引用,React无法检测到状态变化,因此不会触发组件重新渲染。 - 使用defaultValue而非value:
defaultValue仅在组件初始化时生效,后续状态更新无法同步到选择框,导致显示内容不更新。 - 随机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
相关产品推荐
相关产品推荐

