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

ReactJS中基于MUI Autocomplete+Formik实现分类分组下拉选择

MUI Autocomplete + Formik 实现分组下拉选项(分组标题不可选)

核心思路

  1. 重构数据源:将原账户数据转换为带分组标识的结构,含sub_accounts的项作为不可选的分组标题,其下子账户为可选项;无sub_accounts的项直接作为可选项。
  2. 自定义渲染:通过renderOption区分分组与可选项,给分组标题添加样式并禁止点击,子项缩进显示实现层级对齐。
  3. 适配Formik逻辑:确保仅可选项触发值更新,分组标题点击不生效。

修改后的完整代码

import { useState } from 'react';
import { Autocomplete, TextField } from '@mui/material';

export const CashAccountAutocomplete = ({
  field,
  form: { touched, errors, setFieldValue, values },
  disabled,
  ...props
}) => {
  const [inputValue, setInputValue] = useState("");

  const handleChange = (_, newValue, reason) => {
    if (reason === "clear") {
      setFieldValue(field.name, { id: "", name: "" });
      return;
    }
    // 仅处理非分组的可选项
    if (!newValue?.group) {
      setFieldValue(field.name, newValue);
    }
  };

  const handleInputChange = (_, newInputValue) => {
    setInputValue(newInputValue);
  };

  // 构建带分组的选项结构
  const buildGroupedOptions = (accounts) => {
    if (!Array.isArray(accounts)) {
      console.error("Invalid accounts data. Expected an array.");
      return [];
    }

    return accounts.flatMap(account => {
      const { id, name, sub_accounts } = account;
      // 有子账户的,生成分组标题 + 子账户列表
      if (sub_accounts && sub_accounts.length > 0) {
        return [
          { group: true, name }, // 分组标记,不可选
          ...buildGroupedOptions(sub_accounts) // 递归处理子账户
        ];
      }
      // 无子账户的,直接作为可选项
      return [{ id, name }];
    });
  };

  const groupedOptions = buildGroupedOptions(accounts);

  // 自定义过滤逻辑:分组标题和子项都能被输入值匹配
  const filterOptions = (options, { inputValue }) => {
    const lowerInput = inputValue.toLowerCase();
    return options.filter(option => {
      if (option.group) {
        return option.name.toLowerCase().includes(lowerInput);
      }
      return option.name.toLowerCase().includes(lowerInput);
    });
  };

  return (
    <Autocomplete
      {...field}
      disabled={disabled}
      getOptionLabel={(option) =>
        typeof option === "string" ? option : option?.name || ""
      }
      renderOption={(props, option) => {
        if (option.group) {
          // 分组标题:加粗、不可点击、对齐顶层项
          return (
            <li 
              {...props} 
              key={option.name}
              style={{ 
                pointerEvents: 'none', 
                fontWeight: 700,
                paddingLeft: 8 
              }}
            >
              {option.name}
            </li>
          );
        }
        // 可选项:缩进显示层级
        return (
          <li 
            {...props} 
            key={option.id}
            style={{ paddingLeft: 24 }}
          >
            {option.name}
          </li>
        );
      }}
      filterOptions={filterOptions}
      options={groupedOptions || []}
      autoComplete
      includeInputInList
      filterSelectedOptions
      noOptionsText={"No data"}
      onChange={handleChange}
      inputValue={inputValue}
      onInputChange={handleInputChange}
      renderInput={(params) => (
        <TextField
          {...params}
          {...props}
          error={touched[field.name] && errors[field.name]}
          helperText={touched[field.name] && errors[field.name] && String(errors[field.name].id)}
        />
      )}
      fullWidth
    />
  );
};

关键细节说明

  • 分组标识:用group: true标记分组标题,在handleChange中过滤分组项,避免点击分组触发值更新。
  • 样式控制:分组标题设置pointerEvents: 'none'禁止交互,加粗突出显示;子项通过paddingLeft缩进,实现层级对齐。
  • 递归适配:buildGroupedOptions递归遍历账户数据,自动生成嵌套分组结构,支持多层子账户场景。
  • 过滤逻辑:自定义filterOptions确保分组标题和子项都能被输入值匹配,满足搜索需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 11:46:03