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

React+MUI Autocomplete二次打开无选项且持续加载问题

问题解决:Material-UI Autocomplete 关闭后重开无选项且加载持续的问题

问题根源

代码里两个核心逻辑错误导致了该现象:

  1. App组件中存在一个useEffect,在下拉框关闭(open变为false)时执行setOptions([])清空选项,导致再次打开时options为空;
  2. 打开下拉框时未重新触发数据获取,而原loading判断逻辑const loading = open && options.length === 0会持续为true,因为没有新请求填充选项。

同时还需要补充实现无搜索结果时的提示功能。

修复后的完整代码

Auto.tsx

import * as React from "react";
import { Autocomplete, TextField, Typography } from "@mui/material";
import SearchIcon from "@material-ui/icons/Search";
import InputAdornment from "@material-ui/core/InputAdornment";
import CircularProgress from "@material-ui/core/CircularProgress";

interface Option {
  label: string;
  value: number;
}

interface IProps {
  fetchServerSideOptions?: (inputValue: string) => void;
  loading?: boolean;
  open?: boolean;
  onOpen?: (event: React.SyntheticEvent) => void;
  onClose?: (event: React.SyntheticEvent, reason: string) => void;
  options: Option[];
  selected: Option | null;
  setSelected: React.Dispatch<React.SetStateAction<Option | null>>;
}

export default function Auto(props: IProps) {
  const {
    options = [],
    selected,
    setSelected,
    open,
    loading,
    onOpen,
    onClose,
    fetchServerSideOptions
  } = props;
  return (
    <Autocomplete
      forcePopupIcon={false}
      options={options}
      value={selected}
      open={open}
      loading={loading}
      onOpen={onOpen}
      onClose={onClose}
      onChange={(_event, value) => setSelected(value)}
      onInputChange={(_event, value) => fetchServerSideOptions?.(value)}
      renderOption={(props, option) => (
        <li {...props}>
          <Typography>{option.label}</Typography>
        </li>
      )}
      noOptionsText={
        loading ? <CircularProgress size={20} /> : "无匹配选项"
      }
      renderInput={(params) => {
        return (
          <TextField
            {...params}
            placeholder="Select"
            InputProps={{
              ...params.InputProps,
              startAdornment: (
                <React.Fragment>
                  <InputAdornment position="start">
                    <SearchIcon />
                  </InputAdornment>
                  {params.InputProps.startAdornment}
                </React.Fragment>
              ),
              endAdornment: (
                <React.Fragment>
                  {loading ? (
                    <CircularProgress color="inherit" size={20} />
                  ) : null}
                  {params.InputProps.endAdornment}
                </React.Fragment>
              )
            }}
          />
        );
      }}
    />
  );
}

App.tsx

import { useState, useEffect } from "react";
import Auto from "./Auto";

interface Option {
  label: string;
  value: number;
}

export default function App() {
  const [options, setOptions] = useState<Option[]>([]);
  const [selected, setSelected] = useState<Option | null>(null);
  const [open, setOpen] = useState<boolean>(false);
  const [loading, setLoading] = useState<boolean>(false);
  const [currentInput, setCurrentInput] = useState<string>("");

  const fetchOptions = async (value: string) => {
    setLoading(true);
    try {
      const response: Response = await fetch(
        `https://dummyjson.com/products/search?q=${value}&limit=10`
      );
      const { products } = await response.json();
      const formattedOptions = products.map(({ title, id }) => {
        return { label: title, value: id };
      });
      setOptions(formattedOptions);
    } catch (err) {
      console.error("请求失败:", err);
      setOptions([]);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchOptions("");
  }, []);

  useEffect(() => {
    if (open && options.length === 0) {
      fetchOptions(currentInput);
    }
  }, [open, currentInput, options.length]);

  return (
    <Auto
      selected={selected}
      setSelected={setSelected}
      options={options}
      open={open}
      loading={loading}
      onOpen={() => setOpen(true)}
      onClose={() => setOpen(false)}
      fetchServerSideOptions={(value) => {
        setCurrentInput(value);
        fetchOptions(value);
      }}
    />
  );
}

关键修复点

  • 移除关闭下拉框清空选项的逻辑:原逻辑导致再次打开时无初始数据,直接触发加载状态;
  • 独立管理loading状态:不再依赖open和options长度判断加载,改为请求开始/结束时手动控制,避免逻辑冲突;
  • 添加打开下拉框的重新请求逻辑:当下拉框打开且无选项时,根据当前输入值重新获取数据;
  • 实现无结果提示:利用Autocomplete的noOptionsText属性,非加载状态下显示“无匹配选项”;
  • 修复类型不匹配:统一selected属性的类型为Option | null,保证父子组件类型一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 13:23:10