0

I'm trying to use the github api to get a user's projects and list them in a popup window. I'm having trouble figuring out why async / await isn't working and the data i end up passing is always undefined.

This is how i fetch the data from the api (edited to use for... of):

export default async function GitHubFetch({ userName }) {
  let returnArray = [];
  let response = await customFetch(
    Urls.GitHub + "users/" + userName + "/repos"
  );

  for (const element of response) {
    let project = {};
    project.name = element.name;
    project.description = element.description;
    project.html_url = element.html_url;

    let langResponse = await customFetch(element.languages_url);
    project.languages = Object.keys(langResponse);
    returnArray.push(project);
  }
  console.log("the array i'm returning from fetch is: ", returnArray);
  return returnArray;
}

the console.log of returnArray from this function is:

[{"name":"cthulu_finance","description":"stock-trading application written in react and node.js / express","html_url":"https://github.com/contip/cthulu_finance","languages":["TypeScript","HTML","CSS"]},{"name":"c_structures","description":"collection of data structures in c","html_url":"https://github.com/contip/c_structures","languages":["C"]},{"name":"masm_io_procedures","description":"Low-level implementations of string-to-int and int-to-string in x86 assembly","html_url":"https://github.com/contip/masm_io_procedures","languages":["Assembly"]}]

the array of projects from the above function is used to generate the list of projects by this:

export default function GitHubListDisplay({ projects }) {
  let listItems = [];
  console.log(projects);
  if (Array.isArray(projects)) {
    projects.forEach((project, index) => {
      listItems.push(
        <>
          <ListGroup.Item action href={project.html_url}>
            {project.name}
          </ListGroup.Item>
          <ListGroup.Item>{project.description}</ListGroup.Item>
          <ListGroup horizontal>{HorizontalList(project.languages)}</ListGroup>
        </>
      );
    });
  }
  return <ListGroup>{listItems}</ListGroup>;
}

and finally, it's all controlled by this function:

export default function GitHubPopUp({ userName }) {
  const [projectData, setProjectData] = useState([]);

  useEffect(() => {
    async function fetchData() {
      setProjectData(await GitHubFetch({ userName }));
      console.log("the project data i fetched is: ", projectData);
    }
    fetchData();
  }, []);

  return (
    <>
      <OverlayTrigger
        placement="right"
        delay={{ show: 250, hide: 5000 }}
        overlay={
          <Popover>
            <Popover.Title as="h3">{`GitHub Projects`}</Popover.Title>
            <Popover.Content>
              <strong>{userName}'s GitHub Projects:</strong>
              {projectData.length > 0 && (
                <GitHubListDisplay {...projectData} />
              )}
            </Popover.Content>
          </Popover>
        }
      >
        <Button variant="link">{userName}</Button>
      </OverlayTrigger>
    </>
  );
}

From the main controller function, the state eventually gets set correctly, but if i console.log the projectData state directly after awaiting the Fetch function result, it's undefined.. result is:

the project data i fetched is:  []

Additionally, even though i have {projectData.length > 0 && before rendering the GitHubListDisplay component, console.logging the input projects property always results in undefined. The function never ends up displaying anything.

Can anyone please help? I've spent an embarrassing number of hours trying to figure this out.

marsupial
  • 3
  • 3
  • The 'await' used inside the forEach will never block the main thread and the return statement is executed. So your 'GitHubFetch' function always return an empty array. Use promise.all() as pointed out by William. – Naveen Chahar Nov 05 '20 at 07:50

2 Answers2

2

You cannot use forEach indeed for async await as you desire. Just use a modern for … of loop instead, in which await will work as you expected.

Refer to here


However, best practice is to use Promise.all

wangdev87
  • 8,611
  • 3
  • 8
  • 31
  • unfortunately still undefined / same behavior using the for ... of method, as well as the Array.prototype.reduce method given as an alternate to Promise.all from the same thread.. i thought for sure that would work, too : ( – marsupial Nov 05 '20 at 07:51
  • can you update your question with using for...of ? and also attach the returnArray value – wangdev87 Nov 05 '20 at 07:53
  • updated.. i also tried using promise.all with the same result. although i'm not sure if i used it properly – marsupial Nov 05 '20 at 08:03
  • i found the issue, add userName dependency to the use effect – wangdev87 Nov 05 '20 at 08:06
  • useEffect(() => { ... }, [ userName ]); – wangdev87 Nov 05 '20 at 08:07
  • the console.log of returnArray from within the GitHubFetch function, right before it returns, is correct. But the console.log of that array when returned to my useEffect function in GitHubPopUp, is wrong (undefined / empty ) – marsupial Nov 05 '20 at 08:07
  • add `userName` dependency : `useEffect(() => { ... }, [ userName ]);` – wangdev87 Nov 05 '20 at 08:10
  • adding userName to useEffect results in the same behavior : (. something is unfortunately still wrong. i really do appreciate you trying to help though – marsupial Nov 05 '20 at 08:10
  • you mean, the project data i fetched is: [] is the issue? – wangdev87 Nov 05 '20 at 08:12
  • you can't get the projectData value immediately after setProjectData. you need to get it in the useEffect `useEffect(() => { ... }, [ projectData ])` – wangdev87 Nov 05 '20 at 08:13
  • yes. i am awaiting the retrieval of that projectData (i.e. returnArray) in my useEffect function, and we have seen that the GitHubFetch function appears to be returning the correct returnArray from console.log. Unfortunately, the console.log in the main function is empty [], state doesn't get set, and so the list render function receives undefined as prop. the weird thing is, the state DOES eventually get set to the correct array in the main controller function... – marsupial Nov 05 '20 at 08:15
  • as i told, you can't get the projectData value immediately after setProjectData. you need to get it in the useEffect useEffect(() => { ... }, [ projectData ]) – wangdev87 Nov 05 '20 at 08:17
  • when i try: useEffect(() => { ... }, [ projectData ]) , the component infinitely rerenders and continuously prints the two console.log statements we've been discussing – marsupial Nov 05 '20 at 08:20
  • i added a new answer that represents your react codebase – wangdev87 Nov 05 '20 at 08:23
0

export default function GitHubPopUp({ userName }) {
  const [projectData, setProjectData] = useState([]);

  useEffect(() => {
    async function fetchData() {
      setProjectData(await GitHubFetch({ userName }));
    }
    fetchData();
  }, []);
  
  useEffect(() => {
    console.log("the project data i fetched is: ", projectData);
  }, [ projectData ]);

  return (
    <>
      <OverlayTrigger
        placement="right"
        delay={{ show: 250, hide: 5000 }}
        overlay={
          <Popover>
            <Popover.Title as="h3">{`GitHub Projects`}</Popover.Title>
            <Popover.Content>
              <strong>{userName}'s GitHub Projects:</strong>
              {projectData.length > 0 && (
                <GitHubListDisplay {...projectData} />
              )}
            </Popover.Content>
          </Popover>
        }
      >
        <Button variant="link">{userName}</Button>
      </OverlayTrigger>
    </>
  );
}
wangdev87
  • 8,611
  • 3
  • 8
  • 31