【问题标题】:React-Select Async loadOptions is not loading options properlyReact-Select Async loadOptions 未正确加载选项
【发布时间】:2018-10-25 07:53:19
【问题描述】:

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

“反应选择”:“^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 得到响应,则调用这两个函数。所以我从我之前的代码 sn-p 中删除了我的 helper optionsValue 函数,并根据(也在这篇文章中)更新了我的 code-sn-p。但仍然没有运气。在某些情况下,异步选择不起作用。我尝试截取一个这样的案例。我确实在我的本地数据库名称“tamim johnson”中使用了名称,但是当我搜索他时,我没有得到任何响应,但从后端得到了正确的响应。这是这个案例的截图

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

感谢先生抽出宝贵时间。我不知道我做错了什么或遗漏了什么。

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

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

【问题讨论】:

  • noop defaultProp 是这样的,如果它们不包含 actionOnSelectedOption 属性,您可以直接调用它(无条件)。如果 getOptionValue 和 getOptionLabel 没有像我写的那样为您工作,那么您的选项数组中的某些内容不正确。你能发布一个响应的例子吗?
  • @Steve-Cutter-Blades 您好先生,我编辑我的问题以供您回复。非常感谢您的宝贵时间。
  • 您需要发布响应示例。您在 DevTools 中截取了响应选项卡,但预览选项卡使您能够更清晰地扩展响应对象。此外,准确了解您在请求中发送的参数(即每次调用中的 inputValue,以及哪些通过,哪些不通过)
  • @Steve-Cutter-Blades 谢谢您,先生,再次感谢您。我用预览标签更新了我的屏幕截图。
  • 很抱歉,截图太小了,我看不到任何细节。如果您可以附上预览选项卡的屏幕截图,并给我您尝试使用的搜索参数。

标签: reactjs asynchronous react-select react-async


【解决方案1】:

我发现人们打算寻找这个问题。所以我发布了解决问题的代码更新部分。从 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;

【讨论】:

    【解决方案2】:

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

    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. 您不需要映射结果集的方法。有道具 为你处理。
    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。您可以拉出该包装器并尝试您的代码进行测试。

    【讨论】:

      【解决方案3】:

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

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

      不是这样的:

      随后的调用返回将解析为结果的承诺 下一个函数调用

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

      改为使用返回承诺的去抖函数

      例如:

      import debounce from "debounce-promise";
      
      //...
      this.getOptions = debounce(this.getOptions.bind(this), 500);
      

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

      【讨论】:

      • 这一定是一个公认的答案,因为它简单、优雅并且真正解决了为什么从 lodash 去抖动不适合 react-select 的异步。
      • 这是我一直在寻找的解决方案。 github 上的示例指定也应将“leading:true”添加为选项值。在我的情况下,这不是必需的。删除后,它可以完美运行。我有一个搜索字段,在每次击键后触发 API 调用,这个 Promise debounce 现在只在用户完成输入时触发。谢谢! :-)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-06
      • 2022-11-18
      相关资源
      最近更新 更多