12

I would like to get global information from Github user and his repos(and get pinned repos will be awesome). I try to make it with async await but It's is correct? I've got 4 times reRender (4 times console log). It is possible to wait all component to reRender when all data is fetched?

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
  • I haven't ever seen React Hooks before, oops. But have you looked into the async series documentation? It allows you to make one call after the other, then you could setState in the callback once every axios request has succeeded. Commented Apr 4, 2019 at 17:47

4 Answers 4

23

Multiple state updates are batched but but only if it occurs from within event handlers synchronously and not setTimeouts or async-await wrapped methods.

This behavior is similar to classes and since in your case its performing two state update cycles due to two state update calls happening

So Initially you have an initial render and then you have two state updates which is why component renders three times.

Since the two states in your case are related, you can create an object and update them together like this:

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>;
}

Working demo

Sign up to request clarification or add additional context in comments.

2 Comments

the answer works, but there is a much more elegant way. You can just use const [respGlobal, respRepos] = await Promise.all([ axios(api.github.com/users/${username}), axios(api.github.com/users/${username}/repos) ]);
How would you do this if the 2 api calls are unrelated? In this case, /users is called and we wait until it's complete before calling /users/x/repos. What if the data are independent of each other? I don't want one to wait for the other. Is there a pattern for this? I think the same "issue" exists with @FabianHinsenkamp 's answer
1

Figured I'd take a stab at it because the above answer is nice, however, I like cleanliness.

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

Using a self invoking function takes out the extra step of calling the function in useEffect which can sometimes throw Promise errors in IDEs like WebStorm and PHPStorm.

Comments

0
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});

Comments

0

For other researchers (Live demo):

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>
  );
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.