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

React状态存储异常:多输入排序器初始化与值过滤问题

表格数据排序器的状态初始化与选项过滤问题修复

问题概述

开发含3个输入框的表格排序器时遇到两个核心问题:

  • 调用initSelects初始化select状态,预期生成{sorterParam1: 'Date', sorterParam2: '', sorterParam3: ''},但实际仅保留fields数组最后一项的键值对
  • 未实现「已选中的值在后续输入框下拉列表中隐藏」的需求

需求细节

  • 第一个输入框默认值固定为Date,可切换为name、username等其他选项
  • 已选中的值需在后续输入框下拉列表中隐藏:选中Date后,第二、第三个输入框不能显示Date;第二个输入框选中Earth后,第三个输入框不能显示Date和Earth

问题代码

Sorter 组件

/*eslint-disable*/
import React, { useState, useMemo } from 'react';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
import { GridSortModel } from '@mui/x-data-grid';
import SorterField from './SorterField';
const initSelects = (fields) => {
  let object = {};

  fields.map((item) => {
    console.log(item, 'item');
    object = {
      ...item,
      [item.name]: item.defaultValue ? item.defaultValue : '',
    };
  });

  return object;
};

const Sorter = ({ menuItemsValue, setSortData }: SortProps) => {
  const fields: SorterProps[] = [
    {
      name: 'sorterParam1',
      title: 'Sort by',
      optional: false,
      defaultValue: 'Date',
    },
    {
      name: 'sorterParam2',
      title: 'Then by',
      optional: true,
    },
    {
      name: 'sorterParam3',
      title: 'Then by',
      optional: true,
    },
  ];
  const [select, setSelect] = useState<any>(() => initSelects(fields));

  const getMenuItems = useMemo(() => {
    return menuItemsValue.filter((item) => select.sorterParam1 !== item);
  }, [menuItemsValue, select]);

  const handleSelectChange = (
    e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
  ) => {
    const { name, value } = e.target;
    console.log(111, name, value);
    setSelect({ ...select, [name]: value });
    setSortData(sortOptions);
  };

  const handleClearAllInputs = () => {
    setSelect({
      sorterParam1: '',
      sorterParam2: '',
      sorterParam3: '',
    });
  };

  const handleConfirm = () => {};
  return (
    <TextField
      label="Advanced Sorting"
      className={styles.sorter__inputs}
      id="sorter-parameter-1"
      variant="standard"
      InputProps={{
        disableUnderline: true,
      }}
      select
      SelectProps={{
        IconComponent: (props) => <NewIcon {...props} />,
      }}
      sx={{
        fontSize: '12px',
        width: '100%',
        '& .MuiInputBase-input:focus': {
          backgroundColor: 'transparent !important',
        },
        '& .MuiInputLabel-root': {
          color: '#9E9E9E',
        },
        '& .MuiTextField-root': {
          fontSize: '13px',
        },
        '& .MuiOutlinedInput-root': {
          backgroundColor: '#fff',
        },
      }}
    >
      {fields.map((option, index) => (
        <SorterField
          key={option.name}
          menuItemsValue={getMenuItems}
          name={option.name}
          option={option}
          count={fields.length}
          handleChange={handleSelectChange}
          index={index + 1}
        />
      ))}
      <div className={styles.sorter__inputControllers}>
        <Button
          className={styles.sorter__clearAllInput}
          onClick={() => handleClearAllInputs()}
        >
          Clear All
        </Button>
        <Button
          onClick={() => handleConfirm()}
          className={styles.sorter__confirmInput}
        >
          Confirm
        </Button>
      </div>
    </TextField>
  );
};

export default Sorter;

SorterField 组件

/*eslint-disable*/
import React, { useState } from 'react';

import TextField from '@mui/material/TextField';
import { MenuItem } from '@mui/material';

import { SorterProps } from '../../types/Sorter';

import { ReactComponent as SorterLine } from '../../assets/img/sortLine.svg';
import styles from '../../assets/components/Sorter/sorter.module.scss';

type SorterFieldProps = {
  menuItemsValue: string[];
  option: SorterProps;
  count: number;
  name: string;
  handleChange: (
    e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
  ) => void;
  index: number;
};

function SorterField({
  option,
  count,
  menuItemsValue,
  handleChange,
  index,
}: SorterFieldProps) {

  const handleSorting = () => {};

  return (
    <div className={styles.sorter__container}>
      <div className={styles.sorter__header}>
        <p className={styles.sorter__label}>
          {option.title}{' '}
          {option.optional && (
            <sup className={styles.sorter__optional}>*Optional</sup>
          )}
        </p>

        <div className={styles.sorter__numbers__container}>
          {Array.from({ length: count }, (_, i) => i + 1).map((number) => (
            <>
              {number === index ? (
                <>
                  <span className={styles.sorter__number}>{index}</span>
                </>
              ) : (
                <>
                  <span className={styles.sorter__numbers}>{number}</span>
                </>
              )}
            </>
          ))}
        </div>
      </div>
      <div className={styles.sorter__inputs}>
        <TextField
          className={[styles.sorter__input, styles.sorter__field__input].join(
            ' '
          )}
          variant="outlined"
          label="Select"
          select
          SelectProps={{
            IconComponent: () => <NewIcon />,
          }}
          value={option.defaultValue}
          onChange={handleChange}
          name={option.title}
          size="small"
        >
          {menuItemsValue.map((title, idx) => (
            <MenuItem key={idx} value={title}>
              {title}
            </MenuItem>
          ))}
        </TextField>

        <div onClick={handleSorting} className={styles.sorter__sortOrder}>
          <div className={styles.sorter__sortOrder__alphabetical}>
            <span>A</span>
            <span>Z</span>
          </div>

          <div className={styles.sorter__sortOrder__sortLine}>
            <SorterLine />
          </div>
        </div>
      </div>
    </div>
  );
}

export default SorterField;

修复方案

1. 修复状态初始化函数initSelects

原函数使用map循环时每次直接覆盖对象,导致最终仅保留最后一项数据。改用reduce累加字段:

const initSelects = (fields) => {
  return fields.reduce((acc, item) => {
    return {
      ...acc,
      [item.name]: item.defaultValue ?? ''
    };
  }, {});
};

2. 实现全局选中值过滤逻辑

修改getMenuItems,收集所有已选中的非空值并排除,确保后续输入框不显示已选选项:

const getMenuItems = useMemo(() => {
  const selectedValues = Object.values(select).filter(val => val);
  return menuItemsValue.filter(item => !selectedValues.includes(item));
}, [menuItemsValue, select]);

3. 修复SorterField组件的状态绑定错误

  • 将TextField的name属性改为option.name,确保handleChange能正确识别字段
  • 将value属性绑定到select[option.name],实时显示当前选中状态
  • 新增传递select状态到子组件

修改后的Sorter组件渲染部分:

<SorterField
  key={option.name}
  menuItemsValue={getMenuItems}
  name={option.name}
  option={option}
  count={fields.length}
  handleChange={handleSelectChange}
  index={index + 1}
  select={select}
/>

修改后的SorterField组件中TextField部分:

<TextField
  className={[styles.sorter__input, styles.sorter__field__input].join(' ')}
  variant="outlined"
  label="Select"
  select
  SelectProps={{
    IconComponent: () => <NewIcon />,
  }}
  value={select[option.name] ?? ''}
  onChange={handleChange}
  name={option.name}
  size="small"
>

同时更新SorterField的props类型:

type SorterFieldProps = {
  menuItemsValue: string[];
  option: SorterProps;
  count: number;
  name: string;
  handleChange: (
    e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
  ) => void;
  index: number;
  select: Record<string, string>;
};

4. 修正清空按钮的默认值

清空后第一个输入框应恢复默认的Date,而非空值:

const handleClearAllInputs = () => {
  setSelect({
    sorterParam1: 'Date',
    sorterParam2: '',
    sorterParam3: '',
  });
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 13:20:35