Как выполнить вызов API, требующий информации из другого вызова API onSubmit React

#reactjs #api #redux

#reactjs #API #redux

Вопрос:

У меня есть проект react с участием PokeAPI, когда пользователь вводит Pokemon и нажимает кнопку отправки, я хочу, чтобы он получал информацию о Pokemon из API и информацию о типах, которую pokemon имеет из API вместе с ним. По сути, я хочу выполнить вызов api, для которого требуются данные из другого вызова api, и я хочу сделать это onSubmit.

Прямо сейчас я получаю информацию о покемонах onSubmit, а в другом компоненте после отправки он получает информацию о типе, но то, как я это сделал, приводит к повторному рендерингу компонента на неопределенный срок, и я накладываю исправление, которое я действительно хотел бы избежать.

 function handleSubmit(dispatch) {
  return (event) => {
    event.preventDefault();
    const pokemonNameID = new FormData(event.currentTarget).get('pokemon');
    console.log(pokemonNameID);
    api.getPokemon(pokemonNameID)
      .then((response) => {
        dispatch({
          type: 'SELECTED_POKEMON',
          payload: {
            pokemon: response.body,
          }
        })
      })
      .catch((error) => {
        console.log(error);
      });
  }
}
  
   return (
    <form action="" onSubmit={handleSubmit(props.dispatch)}>
      <input className="search" type="text" placeholder="name/id" name="pokemon"/>
      <input className="pkmn-button"type="submit"name="."value="."/>
    </form>
  )
}
Search.propTypes = {
  dispatch: PropTypes.func,
}

export default connect(null)(Search);
  
 class PokemonTypes extends Component {
    shouldUpdate = true;
    count = 0;
    shouldComponentUpdate(){
        if(this.shouldUpdate){
            return true;
        }
        this.count=0;
        this.shouldUpdate = true;
        return false;
    }
    componentDidUpdate(){
        var typeOne=this.props.pokemon.get("types").get(0).get("type").get("name");
        var typeTwo=typeTwoExists(this.props.pokemon);
        var dispatch = this.props.dispatch;
        api.getType1(typeOne)
        .then((response) => {
            dispatch({
              type: 'POKEMON_TYPE1',
              payload: {
                type1: response.body,
              }
            })
          })
          .catch((error) => {
            console.log(error);
          });
        try{
            api.getType2(typeTwo)
            .then((response) => {
                dispatch({
                  type: 'POKEMON_TYPE2',
                  payload: {
                    type2: response.body,
                  }
                })
              })
              .catch((error) => {
                console.log(error);
              });
        }catch(e){
            
        }
          this.count =1;
          if (this.count%2===0){
              this.shouldUpdate = false;
          }
    }
    render() {
        var typeOne=this.props.type1.get("name");
        //this.props.pokemon.get("types").get(0).get("type").get("name");
        var typeTwo=typeTwoExists(this.props.pokemon);
        return (
            <div>
                <IsPokemonTypes type1={typeOne}
                type2={typeTwo} />
             </div>
        )
    }
}

export default connect(mapStateToProps)(PokemonTypes);
  

Как я могу выполнить все вызовы api onSubmit, когда им требуется информация из другого вызова API, который также называется onSubmit ?

Ответ №1:

Если я правильно читаю, сначала мы делаем вызов для извлечения Pokemon, затем, как только мы получим данные Pokemon, мы делаем еще два вызова, чтобы получить типы? Мне не нравится, что эти два типа являются дублированными версиями по существу одного и того же кода, но это не то, о чем вы спрашиваете.

На мой взгляд, лучший способ обработки второго вызова API — это условный рендеринг второго компонента, который инициирует загрузку типов. У вас будет родительский компонент, который обрабатывает загрузку данных Pokemon, а также выбирает их из состояния. Как только этот компонент видит, что данные загружены в состояние, он отображает компонент загрузки типа, передавая реквизиты, которые были установлены в состоянии с первого вызова API. Компонент загрузки типа выполнит второй вызов API, как только он будет смонтирован.

Редактировать:

Второй ответ не зависит от всего первого ответа, он зависит только от одного небольшого фрагмента данных из первого ответа (имена / идентификаторы типов). Итак, мы хотим абстрагировать это в Type компонент, который принимает name только prop .

Что касается наличия одного компонента, который одновременно извлекает и отображает, по сравнению с отдельными компонентами для выборки и рендеринга, основное соображение заключается в том, хотите ли вы повторно использовать одну и ту же логику выборки с несколькими различными форматами отображения на разных страницах вашего приложения. Когда вы станете более продвинутым, вы, вероятно, захотите разбить его, но пока наш компонент будет как извлекать, так и отображать.

Когда вы отправляете тип в redux, мы не хотим, чтобы это действие было привязано к конкретному покемону. Вместо type: 'POKEMON_TYPE1' этого должно быть type: 'RECEIVE_TYPE' . Мы сохраним типы в отдельном разделе состояния. Когда вы обращаетесь к типам для определенного покемона, вы должны использовать селектор redux для выбора имен типов из объекта pokemon, а затем выбрать данные типа из раздела состояния типов. Что на самом деле позволяет довольно легко избежать дублированных вызовов типов, теперь, когда я думаю об этом. Мы передаем имя типа (всегда определенное string ) и данные типа (возможно, полный ответ или, может undefined быть), и нам нужно только инициировать выборку, если данные есть undefined .

Кстати, поскольку вы только учитесь, я бы рекомендовал вам изучать функциональные компоненты и хуки, а не компоненты классов и connect(mapStateToProps) . Вы используете более старый способ, который все еще работает, но почему бы не изучить более новый способ?

Это неполный псевдокод, который потребует изменений и настроек.

 class Type extends Component {
    componentDidMount() {
        if ( ! this.props.data ) {
            api.getType(this.props.name)
                .then((response) => {
                    dispatch({
                        type: 'RECEIVE_TYPE',
                        payload: {
                            name: this.props.name,
                            data: response.body,
                        }
                    })
                })
        } 
    }

    render() {
        <div>
        {this.props.data ? (
            <>{/** your render goes here */}</>
        ) : (
            <LoadingSpinner />
        )}
        </div>
    }
}

const mapStateToProps = (state, {name}) => {
    return {
        data: state.types[name],
    }
}

export default connect(mapStateToProps)(Type)
  

name это обычная поддержка, которая предоставляется напрямую. data поступает из селектора, который ищет данные типа на основе name prop. Поскольку наш компонент подключен, после dispatch отправки componentDidMount this.props.data должен стать определенным.

PokemonTypes Компонент, который принимает объект pokemon в качестве реквизита, сводится практически к нулю, передавая всю работу нашему Type компоненту.

 class PokemonTypes extends Component {
    render() {
        return (
            <div>
                {this.props.pokemon.types.map( typeObject => (
                    <Type name={typeObject.type.name}/>
                ) )}
             </div>
        )
    }
}
  

Я не знаю, как работает ваш пользовательский объект pokemon, поэтому map он основан на формате данных ответа API. Обратите внимание, что нам не нужно иметь дело с typeOne и typeTwo отдельно, когда мы обрабатываем его как массив типов. Если бы мы хотели отобразить два типа по-разному, мы могли бы передать slot в качестве prop Type .

 {this.props.pokemon.types.map(typeObject => (
    <Type
        name={typeObject.type.name}
        slot={typeObject.slot}
    />
))}
  

Комментарии:

1. Да, как только вызов api выполняется для pokemon, я хочу немедленно выполнить еще один вызов api, чтобы получить информацию о 1 или 2 типах, которые есть у pokemon. Я немного новичок во всем этом, поэтому позвольте мне убедиться, что я правильно понял, я передаю информацию о покемонах в другой компонент, например <pokemonType pokemon = {this.props.pokemon} /> а затем в компоненте pokemonType с помощью componentDidMount выполняю вызовы api для типов? Или вы хотите сказать, создать новый компонент, который обрабатывает загрузку специально?

2. Первый — это то, что я предлагал ( <pokemonType pokemon = {this.props.pokemon} /> ) . Но, думая о том, что я знаю о pokemon, ответ от типа на самом деле не зависит от конкретного pokemon, верно? Например, «электрический» — это один и тот же ответ, будь то от Пикачу или Магнемита. Таким образом, реквизитом для Type компонента должно быть имя типа , а не объект pokemon. Я обновлю свой ответ некоторым псевдокодом.

3. Кстати, я не знаю деталей реализации этого созданного вами пользовательского объекта this.props.pokemon.get("types").get(0).get("type").get("name"); , я предполагаю, что вы делаете это как способ убедиться, что свойства существуют, вместо того, чтобы просто обращаться к ним напрямую и потенциально иметь ошибки. Я привык к подобным вещам, но потом я начал использовать Typescript и никогда не оглядывался назад!