如何解决异步搜索选项延迟?

How to solve async search options delay?

我正在创建一个 去抖标签搜索表单,它应该获取选项和 return searchResults 来提供 loadOptions.

问题:由于去抖动,"correct" 收到的选项和显示的选项之间存在一致的延迟。 "correct options" 会在下次调用时显示(最少一个字符)。

想法(可能不是最好的):我想async/awaitloadOptions()然后等待useSearchTags()到return.有人在那里 (https://github.com/JedWatson/react-select/issues/3145#issuecomment-434286534) 遇到了同样的问题并分享了解决方案。我的情况有点不同,因为我没有直接获取 loadOptions()。有什么想法吗?

Codesandbox 最小示例

https://codesandbox.io/s/debounce-react-select-loadoptions-tgud8?file=/src/App.js

捕获

代码

// helpers/useDebouncedSearch.js

import { useState } from 'react';
import AwesomeDebouncePromise from 'awesome-debounce-promise';
import { useAsync } from 'react-async-hook';
import useConstant from 'use-constant';
import to from 'await-to-js';


const useDebouncedSearch = (searchFunction) => {
  const [inputText, setInputText] = useState('');

  const debouncedSearchFunction = useConstant(() =>
    AwesomeDebouncePromise(searchFunction, 300)
  );

  const searchResults = useAsync(
    async () => {
      if (inputText.length === 0) {
        return [];
      } else {
        let [err, debouncedResults] = await to(debouncedSearchFunction(inputText));
        if(err) return [];

        // reformat tags to match AsyncSelect config
        const refactorTags = (tags) => {
          return tags.map(tag => ({ label: tag.label, value: tag._id }))    
        }

        return (debouncedResults.length !== 0) ? 
          refactorTags(debouncedResults) : 
          [];
      }
    },
    [debouncedSearchFunction, inputText]
  );

  return {
    inputText,
    setInputText,
    searchResults
  };
}

export default useDebouncedSearch;
// SearchTags.js

import React, { useRef } from 'react';
import api from '../blablalivre-api.js';
import useDebouncedSearch from '../helpers/useDebouncedSearch.js';
import AsyncCreatableSelect from 'react-select/async-creatable';

import './SearchTags.scss';

const fetchTags = async text => 
  (await api.searchTags(text));

const useSearchTags = () => useDebouncedSearch(text => fetchTags(text));

const SearchTagsRender = (props) => {
  const { inputText, setInputText, searchResults } = useSearchTags();

  const loadOptions = async (inputValue) => {
    console.log('searchResults.result: ', searchResults.result);
    return searchResults.result;
  }

  const handleOnChange = (tags) => {
    props.updateTagsSelections(tags);
  }

  // issue AsyncCreatableSelect: https://github.com/JedWatson/react-select/issues/3412

  return (
    <AsyncCreatableSelect
      isCreatable
      isMulti
      inputValue={inputText}
      onInputChange={setInputText}
      onChange={handleOnChange}
      loadOptions={loadOptions}
      cacheOptions
      placeholder='Ajouter une thématique'
      isClearable={false}
      id='search-tags'
      classNamePrefix='search-tags'
      // to hide menu when input length === 0
      openMenuOnClick={false}
      // to remove dropdown icon
      components={{ DropdownIndicator:() => null, IndicatorSeparator:() => null }}
      // to custom display when tag is unknown
      formatCreateLabel={inputValue => `Ajouter "${inputValue}"`}
      // to reset focus after onChange = needs to user Refs
    />
  );
};

export default SearchTagsRender;

非常感谢您的帮助!

皮埃尔

问题在于在您的案例中如何实施 loadOptions。 loadOptions 需要为 AsyncSelect 提供一个承诺,该承诺会在值可用时解决。但是,当您使用 useAsync 提供搜索结果时,它 return 最初会向您提供加载值的响应,然后重新呈现会导致它 return 响应可用时的结果

但是,在您的情况下,loadOptions returns searchResults.result 在加载状态期间是 undefined

现在由于 loadOptions 被解析为未定义的值,在下次重新渲染时它不会使用该值,除非输入被更改

这里的解决方案不是不使用 useAsync 而是提供 searchResults 作为 loadOptions 函数

const SearchTagsRender = props => {
  const { inputText, setInputText, loaadSearchResults } = useSearchTags();

  console.log(loaadSearchResults);
  const handleOnChange = tags => {
    const tagsFromForm = tags || [];

    props.updateTagsFromForm(tagsFromForm);
  };

  return (
    <>
      <AsyncCreatableSelect
        isCreatable
        isMulti
        inputValue={inputText}
        onInputChange={setInputText}
        onChange={handleOnChange}
        loadOptions={loaadSearchResults}
        cacheOptions
        placeholder="Ajouter une thématique"
        isClearable={false}
        id="search-tags"
        classNamePrefix="search-tags"
        // to hide menu when input length === 0
        openMenuOnClick={false}
        // to remove dropdown icon
        components={{
          DropdownIndicator: () => null,
          IndicatorSeparator: () => null
        }}
        // to custom display when tag is unknown
        formatCreateLabel={inputValue => inputValue}
        // to reset focus after onChange = needs to user Refs
      />
    </>
  );
};

export default SearchTagsRender;

const useDebouncedSearch = searchFunction => {
  const [inputText, setInputText] = useState("");

  const debouncedSearchFunction = useConstant(() =>
    AwesomeDebouncePromise(searchFunction, 300)
  );

  const loaadSearchResults = async () => {
    if (inputText.length === 0) {
      return [];
    } else {
      let [err, debouncedResults] = await to(
        debouncedSearchFunction(inputText)
      );
      if (err) return [];

      console.log("debouncedResults: ", debouncedResults);

      // reformat tags to match AsyncSelect config
      const refactorItems = items => {
        return items.map(item => ({
          label: item.name,
          value: item.alpha3Code
        }));
      };

      return debouncedResults.length !== 0
        ? refactorItems(debouncedResults)
        : [];
    }
  };

  return {
    inputText,
    setInputText,
    loaadSearchResults
  };
};

Working demo