代码之家  ›  专栏  ›  技术社区  ›  lrr59

在第二次调用API后,useState不设置状态:MUI+RreactJS

  •  0
  • lrr59  · 技术社区  · 3 年前

    我正在使用搜索框的材料UI自动完成组件,但遇到了问题。这些选项是从API获取并传递给组件的。第一次打开下拉列表时,选项列表会按预期显示。但是,当我再次关闭并打开下拉列表时,它没有显示任何选项,并且加载图标持续出现。我想要的功能是,用户可以在开始时获取选项,然后在键入时,重新蚀刻选项并显示加载图标。当没有结果时,它应该返回“未找到选项”。

    沙盒: https://codesandbox.io/s/autocomplete-with-typescript-forked-3l2777?file=/src/App.tsx

    import * as React from "react";
    import { Autocomplete, TextField } 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;
      setSelected: React.Dispatch<React.SetStateAction<Option>>;
    }
    
    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)}
          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>
                  )
                }}
              />
            );
          }}
        />
      );
    }
    
    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 = open && options.length === 0;
    
      const fetchOptions = async (value: string) => {
        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 };
        });
        console.log(formattedOptions);
        setOptions(formattedOptions);
      };
    
      useEffect(() => {
        fetchOptions("");
      }, []);
    
      useEffect(() => {
        if (!open) {
          setOptions([]);
        }
      }, [open]);
    
      return (
        <Auto
          selected={selected}
          setSelected={setSelected}
          options={options}
          open={open}
          loading={loading}
          onOpen={() => setOpen(true)}
          onClose={() => setOpen(false)}
          fetchServerSideOptions={(value) => fetchOptions(value)}
        />
      );
    }
    
    1 回复  |  直到 3 年前
        1
  •  2
  •   DecPK    3 年前

    您可以删除第二个 useEffect 这是不需要的。

      useEffect(() => {
        if (!open) {
          setOptions([]);
        }
      }, [open]);
    

    添加一个状态,显示何时加载(当API调用正在进行时),而不是当长度为 0

      const [loading, setLoading] = useState(false);
      // const loading = open && options.length === 0;
    

    LIVE DEMO

    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(false);
      // const loading = open && options.length === 0;
    
      const fetchOptions = async (value: string) => {
        try {
          setLoading(true); // CHANGE
          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 };
          });
          console.log(formattedOptions);
          setOptions(formattedOptions);
        } catch (error) {
          console.error(error);
        } finally {
          setLoading(false); // CHANGE
        }
      };
    
      useEffect(() => {
        fetchOptions("");
      }, []);
    
      // useEffect(() => {
      //   if (!open) {
      //     setOptions([]);
      //   }
      // }, [open]);
    
      return (
        <Auto
          selected={selected}
          setSelected={setSelected}
          options={options}
          open={open}
          loading={loading}
          onOpen={() => setOpen(true)}
          onClose={() => setOpen(false)}
          fetchServerSideOptions={(value) => fetchOptions(value)}
        />
      );
    }