ReactJS中基于MUI Autocomplete+Formik实现分类分组下拉选择
MUI Autocomplete + Formik 实现分组下拉选项(分组标题不可选)
核心思路
- 重构数据源:将原账户数据转换为带分组标识的结构,含
sub_accounts的项作为不可选的分组标题,其下子账户为可选项;无sub_accounts的项直接作为可选项。 - 自定义渲染:通过
renderOption区分分组与可选项,给分组标题添加样式并禁止点击,子项缩进显示实现层级对齐。 - 适配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
相关产品推荐
相关产品推荐

