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

问题描述 投票:0回答:1

我正在创建一个去抖动的标签搜索表单,该表单应获取选项并返回searchResults以供以loadOptions

问题:由于存在反跳,因此“正确”的接收选项和显示的选项之间存在一致的延迟。下次呼叫时显示“正确的选项”(最少一个字符)。

想法(可能不是最好的):我想异步/等待loadOptions()并等待useSearchTags()返回。那里有人遇到相同的问题(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

捕获

On first character: no option displayedOn second character: options from first character response displayed

代码

// 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;

非常感谢您的帮助!

皮埃尔

javascript reactjs react-select
1个回答
0
投票

问题在于您的情况下如何实现loadOptions。 loadOptions需要为AsyncSelect提供一个Promise,该Promise会在值可用时进行解析。但是,当您使用useAsync提供searchResults时。它首先返回一个带有加载值的响应,然后在响应可用时进行重新渲染,使其返回结果。

但是,在您的情况下,loadOptions从loadOptions返回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

© www.soinside.com 2019 - 2024. All rights reserved.