web-dev-qa-db-ja.com

React-Select Async loadOptionsはオプションを適切にロードしていません

React Async Select loadoptionがオプションのロードに失敗することがあります。これは、いくつかのクエリセットがloadoptionsに反応しても値がロードされない後の非常に奇妙な現象ですが、ログから結果がバックエンドクエリから適切に取得されたことがわかります。私のコードベースは完全に最新であり、react-selectの新しいリリースと使用しています

「react-select」:「^ 2.1.1」

次に、react-async selectコンポーネントのフロントエンドコードを示します。 getOptions関数でデバウンスを使用して、バックエンド検索クエリの数を減らしています。これは私が推測する問題を引き起こさないはずです。この場合に観察する別のポイントを追加したいと思います。ロードオプション検索インジケータ(...)もこの現象には表示されません。

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. デバウンスを使用せずにテストを行いますが、それでも運はありません。私はバックエンド呼び出しを制限するためにデバウンスを使用しています。そうしないと、キーストロークごとにバックエンド呼び出しが必要になる可能性があります。

非同期選択は2/3クエリに対して完全に機能し、その後突然機能しなくなります。これらのケースでは、検索インジケータ(...)も表示されないことがわかります。

お時間をありがとうございました。

スティーブの答えに答えるために2を編集してください

返信ありがとうございます。 getOptionValueとgetOptionLabelについて間違っていました。 loadOptionsが応答を取得した場合、これらの関数の両方が呼び出されます。そのため、以前のコードスニペットからヘルパーoptionsValue関数を削除し、(この投稿でも)に従ってコードスニペットを更新します。しかし、まだ運はありません。場合によっては、非同期選択が機能しませんでした。そのような場合のスクリーンショットを撮ろうとします。 local-dbの名前「tamim johnson」で名前を使用していますが、彼を検索すると、応答がありませんでしたが、バックエンドから適切な応答が返されました。これがこのケースのスクリーンショットです tamim johnson

このスクリーンショットがどれほど明確かはわかりません。 Tamim johnsonも私のランクリストの6位にいます。

お時間をいただきありがとうございます。私は何を間違っているのか、何かを見逃しているのか見当がつきません。

スティーブの答えに答えるために3を編集してください

これは、「tamim johnson」という名前のユーザー検索のプレビュータブ応答です。

preview tab

6
Shakil

人々はこの問題を探すつもりであることがわかりました。そこで、問題を修正するコードの更新部分を投稿しています。 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;
8
Shakil

コードの下にいくつかのメモがあります。次のようなものを探しています:

_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. actionOnSelectedOptionnoopメソッドにデフォルト設定すると、それを呼び出すための条件は不要になります。
  4. React-Selectは内部的にinputValueを追跡します。外部(ラッパー)に何らかの必要性がない限り、その状態を管理しようとする必要はありません。
  5. defaultOptionsは次のいずれかです。
    • デフォルトオプションの配列(フィルタリングするまでloadOptionsを呼び出しません)
    • trueloadOptionsメソッドから自動ロードします)
  6. Async/Await関数は、callback型ではなくpromise応答を使用して、promiseを返します。

getOptions()メソッドをdebounceでラップすることにより、コンポーネントでthisスコープを壊しているのではないかと思っています。 debounceを使用したことがないので、確かに言うことはできません。そのラッパーをプルして、コードをテストしてみてください。

問題は、Lodashのデバウンス機能がこれに適していないことです。 Lodashは

デバウンスされた関数への後続の呼び出しは、最後のfunc呼び出しの結果を返します

しないこと:

後続の呼び出しは、次のfunc呼び出しの結果に解決されるpromiseを返します

これは、デバウンスされた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

2