React-Select Async loadOptions 未正确加载选项

IT技术 reactjs asynchronous react-select react-async
2021-05-24 04:07:51

React Async Select loadoption 有时无法加载选项。在一组查询react loadoptions 不加载任何值后,这是一个非常奇怪的现象,但我可以从日志中看到结果正确来自后端查询。我的代码库与 react-select 新版本和使用完全同步

“react选择”:“^2.1.1”

这是我的 react-async 选择组件的前端代码。我确实在我的 getOptions 函数中使用了 debounce 来减少后端搜索查询的数量。我猜这应该不会造成任何问题。我想补充一点,我在这种情况下观察到的,loadoptions serach 指标 ( ... ) 也没有出现在这种现象中。

import React from 'react';
import AsyncSelect from 'react-select/lib/Async';
import Typography from '@material-ui/core/Typography';
import i18n from 'react-intl-universal';

const _ = require('lodash');

class SearchableSelect extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      inputValue: '',
      searchApiUrl: props.searchApiUrl,
      limit: props.limit,
      selectedOption: this.props.defaultValue
    };
    this.getOptions = _.debounce(this.getOptions.bind(this), 500);
    //this.getOptions = this.getOptions.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.noOptionsMessage = this.noOptionsMessage.bind(this);
    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleChange(selectedOption) {
    this.setState({
      selectedOption: selectedOption
    });
    if (this.props.actionOnSelectedOption) {
      // this is for update action on selectedOption
      this.props.actionOnSelectedOption(selectedOption.value);
    }
  }

  handleInputChange(inputValue) {
    this.setState({ inputValue });
    return inputValue;
  }

  async getOptions(inputValue, callback) {
    console.log('in getOptions'); // never print
    if (!inputValue) {
      return callback([]);
    }
    const response = await fetch(
      `${this.state.searchApiUrl}?search=${inputValue}&limit=${
        this.state.limit
      }`
    );
    const json = await response.json();
    console.log('results', json.results); // never print
    return callback(json.results);
  }

  noOptionsMessage(props) {
    if (this.state.inputValue === '') {
      return (
        <Typography {...props.innerProps} align="center" variant="title">
          {i18n.get('app.commons.label.search')}
        </Typography>
      );
    }
    return (
      <Typography {...props.innerProps} align="center" variant="title">
        {i18n.get('app.commons.errors.emptySearchResult')}
      </Typography>
    );
  }
  getOptionValue = option => {
    return option.value || option.id;
  };

  getOptionLabel = option => {
    return option.label || option.name;
  };

  render() {
    const { defaultOptions, placeholder } = this.props;
    return (
      <AsyncSelect
        cacheOptions
        value={this.state.selectedOption}
        noOptionsMessage={this.noOptionsMessage}
        getOptionValue={this.getOptionValue}
        getOptionLabel={this.getOptionLabel}
        defaultOptions={defaultOptions}
        loadOptions={this.getOptions}
        placeholder={placeholder}
        onChange={this.handleChange}
      />
    );
  }
}

export default SearchableSelect;

编辑以回应史蒂夫的回答

谢谢你的回答史蒂夫。仍然没有运气。我尽量根据你的回答点来回答。

  1. 如果我不使用 optionsValue,而是使用 getOptionValue 和 getOptionLevel,则查询结果不会正确加载。我的意思是加载了空白选项,没有文本值。
  2. 是的,你是对的,是一个返回字符串的同步方法,我不需要覆盖它。而且这个工作正常并且 noOptionsMessage 显示正确。感谢指出这一点。
  3. actionOnSelectedOption 不是一个 noop 方法,它可能有一些责任来执行。我尝试使用 SearchableSelect 作为独立组件,如果我需要一些后端操作来执行此功能,则会相应地触发该操作。例如,我在我的项目的用户配置文件中使用它,用户可以在其中从现有条目更新他的学校/学院信息。当用户选择一个选项时,需要执行配置文件更新责任。
  4. 是的,你是对的。我不需要保持 inputValue 的状态,谢谢。
  5. 我确实确保 defaultOptions 是一个数组。
  6. 我在不使用 debounce 的情况下进行了测试,但仍然没有运气。我正在使用 debounce 来限制后端调用,否则可能会有我肯定不想要的每个击键的后端调用。

异步选择非常适合 2/3 查询,之后它突然停止工作。我观察到一种可区分的行为,对于这些情况,搜索指标 (...) 也没有显示。

非常感谢您的时间。

编辑 2 以回应史蒂夫的回答

再次感谢您的回复。我对 getOptionValue 和 getOptionLabel 的看法是错误的。如果 loadOptions 得到响应,则调用这两个函数。所以我从我之前的代码片段中删除了我的 helper optionsValue 函数,并根据(也在这篇文章中)更新我的代码片段。但仍然没有运气。在某些情况下 async-select 不起作用。我尝试在这样的情况下截取屏幕截图。我确实在我的本地数据库名称“tamim johnson”中使用了名称,但是当我搜索他时,我没有得到任何响应,但从后端得到了正确的响应。这是这个案例的截图 塔米姆约翰逊

我不确定这张截图有多清晰。Tamim johnson 在我的排名中也排在第 6 位。

谢谢先生您的时间。我不知道我做错了什么或遗漏了什么。

编辑 3 以回应史蒂夫的回答

这是名为“tamim johnson”的用户搜索的预览选项卡响应。

预览标签

3个回答

我发现人们打算寻找这个问题。所以我发布了解决问题的代码的更新部分。从 async-await 转换为普通回调函数解决了我的问题。特别感谢史蒂夫和其他人。

import React from 'react';
import AsyncSelect from 'react-select/lib/Async';
import { loadingMessage, noOptionsMessage } from './utils';
import _ from 'lodash';

class SearchableSelect extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      selectedOption: this.props.defaultValue
    };
    this.getOptions = _.debounce(this.getOptions.bind(this), 500);
  }

  handleChange = selectedOption => {
    this.setState({
      selectedOption: selectedOption
    });
    if (this.props.actionOnSelectedOption) {
      this.props.actionOnSelectedOption(selectedOption.value);
    }
  };

  mapOptionsToValues = options => {
    return options.map(option => ({
      value: option.id,
      label: option.name
    }));
  };

  getOptions = (inputValue, callback) => {
    if (!inputValue) {
      return callback([]);
    }

    const { searchApiUrl } = this.props;
    const limit =
      this.props.limit || process.env['REACT_APP_DROPDOWN_ITEMS_LIMIT'] || 5;
    const queryAdder = searchApiUrl.indexOf('?') === -1 ? '?' : '&';
    const fetchURL = `${searchApiUrl}${queryAdder}search=${inputValue}&limit=${limit}`;

    fetch(fetchURL).then(response => {
      response.json().then(data => {
        const results = data.results;
        if (this.props.mapOptionsToValues)
          callback(this.props.mapOptionsToValues(results));
        else callback(this.mapOptionsToValues(results));
      });
    });
  };

  render() {
    const { defaultOptions, placeholder, inputId } = this.props;
    return (
      <AsyncSelect
        inputId={inputId}
        cacheOptions
        value={this.state.selectedOption}
        defaultOptions={defaultOptions}
        loadOptions={this.getOptions}
        placeholder={placeholder}
        onChange={this.handleChange}
        noOptionsMessage={noOptionsMessage}
        loadingMessage={loadingMessage}
      />
    );
  }
}

export default SearchableSelect;

可以在代码下方找到一些注释。你正在寻找这样的东西:

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/lib/Async';
import debounce from 'lodash.debounce';
import noop from 'lodash.noop';
import i18n from 'myinternationalization';

const propTypes = {
  searchApiUrl: PropTypes.string.isRequired,
  limit: PropTypes.number,
  defaultValue: PropTypes.object,
  actionOnSelectedOption: PropTypes.func
};

const defaultProps = {
  limit: 25,
  defaultValue: null,
  actionOnSelectedOption: noop
};

export default class SearchableSelect extends Component {
  static propTypes = propTypes;
  static defaultProps = defaultProps;
  constructor(props) {
    super(props);
    this.state = {
      inputValue: '',
      searchApiUrl: props.searchApiUrl,
      limit: props.limit,
      selectedOption: this.props.defaultValue,
      actionOnSelectedOption: props.actionOnSelectedOption
    };
    this.getOptions = debounce(this.getOptions.bind(this), 500);
    this.handleChange = this.handleChange.bind(this);
    this.noOptionsMessage = this.noOptionsMessage.bind(this);
    this.handleInputChange = this.handleInputChange.bind(this);
  }

  getOptionValue = (option) => option.id;

  getOptionLabel = (option) => option.name;

  handleChange(selectedOption) {
    this.setState({
      selectedOption: selectedOption
    });
    // this is for update action on selectedOption
    this.state.actionOnSelectedOption(selectedOption.value);
  }

  async getOptions(inputValue) {
    if (!inputValue) {
      return [];
    }
    const response = await fetch(
      `${this.state.searchApiUrl}?search=${inputValue}&limit=${
      this.state.limit
      }`
    );
    const json = await response.json();
    return json.results;
  }

  handleInputChange(inputValue) {
    this.setState({ inputValue });
    return inputValue;
  }

  noOptionsMessage(inputValue) {
    if (this.props.options.length) return null;
    if (!inputValue) {
      return i18n.get('app.commons.label.search');
    }

    return i18n.get('app.commons.errors.emptySearchResult');
  }

  render() {
    const { defaultOptions, placeholder } = this.props;
    const { selectedOption } = this.state;
    return (
      <AsyncSelect
        cacheOptions
        value={selectedOption}
        noOptionsMessage={this.noOptionsMessage}
        getOptionValue={this.getOptionValue}
        getOptionLabel={this.getOptionLabel}
        defaultOptions={defaultOptions}
        loadOptions={this.getOptions}
        placeholder={placeholder}
        onChange={this.handleChange}
      />
    );
  }
}
  1. 您不需要映射结果集的方法。有props可以为您处理。
  2. 如果您i18n.get()是返回字符串的同步方法,则不必覆盖整个组件(即使是样式更改)
  3. 如果您默认actionOnSelectedOption使用一个noop方法,那么您不再需要条件来调用它。
  4. React-Select 在inputValue内部跟踪除非您有外部需求(您的包装器),否则无需尝试管理它的状态。
  5. defaultOptions 或者是
    • 一组默认选项(在loadOptions过滤之前不会调用
    • true(将从您的loadOptions方法自动加载
  6. Async/Await 函数返回一个Promise,使用Promise响应而不是callback类型。

我想知道,通过将您的getOptions()方法包装在 中debounce,您是否打破this了组件的范围。不能肯定,因为我以前从未使用debounce过。你可以拉那个包装器并尝试你的代码来测试。

问题是 Lodash 的 debounce 功能不适合这个。Lodash 规定

对去抖动函数的后续调用返回最后一次 func 调用的结果

不是那个:

后续调用返回Promise,它将解析为下一次 func 调用的结果

这意味着在等待期内对 debounced loadOptions prop 函数的每个调用实际上都在返回最后一个 func 调用,因此我们关心的“真实”Promise永远不会被订阅。

而是使用返回Promise的 debounce 函数

例如:

import debounce from "debounce-promise";

//...
this.getOptions = debounce(this.getOptions.bind(this), 500);

查看完整解释https://github.com/JedWatson/react-select/issues/3075#issuecomment-450194917