Несколько осей выборки данных с помощью React Hooks

#reactjs #axios #react-hooks

#reactjs #axios #реагирующие крючки

Вопрос:

Я хотел бы получить глобальную информацию от пользователя Github и его репозиториев (и закрепленные репозитории будут потрясающими). Я пытаюсь сделать это с помощью async await, но это правильно? У меня 4 раза повторный запуск (4 раза журнал консоли). Можно ли дождаться повторного запуска всего компонента, когда будут извлечены все данные?

 function App() {
  const [data, setData] = useState(null);
  const [repos, setRepos] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const respGlobal = await axios(`https://api.github.com/users/${username}`);
      const respRepos = await axios(`https://api.github.com/users/${username}/repos`);

      setData(respGlobal.data);
      setRepos(respRepos.data);
    };

    fetchData()

  }, []);

  if (data) {
    console.log(data, repos);
  }

  return (<h1>Hello</h1>)
}
  

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

1. Я никогда раньше не видел React Hooks, упс. Но вы смотрели документацию по async series ? Это позволяет вам выполнять один вызов за другим, затем вы можете setState выполнять обратный вызов после успешного выполнения каждого запроса axios.

Ответ №1:

Несколько обновлений состояния отправляются пакетно, но только в том случае, если это происходит синхронно из обработчиков событий, а не setTimeouts или async-await wrapped methods .

Это поведение аналогично классам, и поскольку в вашем случае оно выполняет два цикла обновления состояния из-за двух вызовов обновления состояния

Итак, сначала у вас есть начальный рендеринг, а затем у вас есть два обновления состояния, поэтому компонент отображается три раза.

Поскольку два состояния в вашем случае связаны, вы можете создать объект и обновить их вместе следующим образом:

 function App() {
  const [resp, setGitData] = useState({ data: null, repos: null });

  useEffect(() => {
    const fetchData = async () => {
      const respGlobal = await axios(
        `https://api.github.com/users/${username}`
      );
      const respRepos = await axios(
        `https://api.github.com/users/${username}/repos`
      );

      setGitData({ data: respGlobal.data, repos: respGlobal.data });
    };

    fetchData();
  }, []);

  console.log('render');
  if (resp.data) {
    console.log("d", resp.data, resp.repos);
  }

  return <h1>Hello</h1>;
}
  

Рабочая демонстрация

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

1. ответ работает, но есть гораздо более элегантный способ. Вы можете просто использовать const [respGlobal, respRepos] = await Promise.all([ axios( api.github.com/users /$ {имя пользователя} ), axios( api.github.com/users /$ {имя пользователя}/ репозитории ) ]);

2. Как бы вы это сделали, если 2 вызова api не связаны? В этом случае вызывается /users, и мы ждем его завершения, прежде чем вызывать /users /x / repos. Что, если данные независимы друг от друга? Я не хочу, чтобы один ждал другого. Есть ли шаблон для этого? Я думаю, что такая же «проблема» существует с ответом @ FabianHinsenkamp

Ответ №2:

Подумал, что я бы попробовал это, потому что приведенный выше ответ хорош, однако мне нравится чистота.

 import React, { useState, useEffect } from 'react'
import axios from 'axios'

const Test = () => {
    const [data, setData] = useState([])

    useEffect(() => {
        (async () => {
            const data1 = await axios.get('https://jsonplaceholder.typicode.com/todos/1')
            const data2 = await axios.get('https://jsonplaceholder.typicode.com/todos/2')
            setData({data1, data2})
        })()
    }, [])

    return JSON.stringify(data)
}

export default Test
  

Использование самозапускающейся функции устраняет дополнительный этап вызова функции в useEffect , который иногда может вызывать ошибки Promise в IDE, таких как WebStorm и PhpStorm.

Ответ №3:

 function App() {
  const [resp, setGitData] = useState({ data: null, repos: null });

  useEffect(() => {
    const fetchData = async () => {
      const respGlobal = await axios(
        `https://api.github.com/users/${username}`
      );
      const respRepos = await axios(
        `https://api.github.com/users/${username}/repos`
      );

      setGitData({ data: respGlobal.data, repos: respGlobal.data });
    };

    fetchData();
  }, []);

  console.log('render');
  if (resp.data) {
    console.log("d", resp.data, resp.repos);
  }

  return <h1>Hello</h1>;
}

he made some mistake here:
setGitData({ data: respGlobal.data, repos: respGlobal.data(respRepos.data //it should be respRepos.data});
  

Ответ №4:

Для других исследователей (живая демонстрация):

 import React, { useEffect, useState } from "react";
import { CPromise, CanceledError } from "c-promise2";
import cpAxios from "cp-axios";

function MyComponent(props) {
  const [error, setError] = useState("");
  const [data, setData] = useState(null);
  const [repos, setRepos] = useState(null);

  useEffect(() => {
    console.log("mount");
    const promise = CPromise.from(function* () {
      try {
        console.log("fetch");
        const [respGlobal, respRepos] = [
          yield cpAxios(`https://api.github.com/users/${props.username}`),
          yield cpAxios(`https://api.github.com/users/${props.username}/repos`)
        ];

        setData(respGlobal.data);
        setRepos(respRepos.data);
      } catch (err) {
        console.warn(err);
        CanceledError.rethrow(err); //passthrough
        // handle other errors than CanceledError
        setError(err   "");
      }
    }, []);

    return () => {
      console.log("unmount");
      promise.cancel();
    };
  }, [props.username]);

  return (
    <div>
      {error ? (
        <span>{error}</span>
      ) : (
        <ul>
          <li>{JSON.stringify(data)}</li>
          <li>{JSON.stringify(repos)}</li>
        </ul>
      )}
    </div>
  );
}