0

I've been trying to use the data I get from an Async function inside of another function I use to display HTML on a react project. I have made several attempts but nothing seems to work for me. Hope any of you could help me. Please correct me if I did anything wrong.

I've tried it with a useEffect as well:

import React, { useState, useEffect } from 'react';
import { getGenres } from './api/functions';

const ParentThatFetches = () => {
  const [data, updateData] = useState();
  useEffect(() => {
    const getData = async () => {
      const genres = await getGenres('tv');
      updateData(genres);
    }
    getData();
  }, []);

  return data && <Screen data={data} />
}

const Screen = ({data}) => {
  console.log({data}); //logs 'data: undefined' to the console
  return (
    <div>
      <h1 className="text-3xl font-bold underline">H1</h1>
    </div>
  );
}


export default Screen;

The Error I get from this is: {data: undefined}.

The getGenres function that makes the HTTP Request:

const apiKey = 'key';
const baseUrl = 'https://api.themoviedb.org/3';

export const getGenres = async (type) => {
    const requestEndpoint = `/genre/${type}/list`;
    const requestParams = `?api_key=${apiKey}`;
    const urlToFetch = baseUrl + requestEndpoint + requestParams;
    try {
        const response = await fetch(urlToFetch);
        if(response.ok) {
            const jsonResponse = await response.json();
            const genres = jsonResponse.genres;
            return genres;
        }
    } catch(e) {
        console.log(e);
    }
}

I want to use the data inside my HTML, so the H1 for example. Once again, haven't been doing this for a long time so correct me if I'm wrong.

  • Does this answer your question? [How to return the response from an asynchronous call](https://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-asynchronous-call) – Heretic Monkey May 03 '22 at 11:24
  • `console.log({data}); //returns undefined` that seems unlikely to be occuring if you're only rendering `Screen` with the `data` prop when it's truthy (which is what you're code is doing)... perhaps you're rendering it somewhere else. Your second approach looks like it should work Creatine a [mre] would help with debugginng this issue – Nick Parsons May 03 '22 at 11:32
  • `const genres = App();` doesn't make much sense. While it maybe a function it's a React _function component_ and can't be called like a normal function like you're doing. React expects JSX to be returned from a component. Maybe `App` should be calling `getGenres`, updating state, and then passing that state down to `Screen`. – Andy May 03 '22 at 11:38
  • `async` functions by definition return a Promise ... note ... such a simple async function that simply returns the only awaited promise, i.e. your `async function App() { const genres = await getGenres('tv'); return genres; }` is equivalent to `function App() { return getGenres('tv'); }` - note the lack of async/await - yet it returns the identical result - you've fallen for the newbie trap, that somehow async/await can turn asynchrony synchronous - but that's not possible at all, since the future can't be predicted – Bravo May 03 '22 at 11:39
  • OP please don't edit your question using the information in the comments. The comments become redundant because the question is different, and then we're back to square one. – Andy May 03 '22 at 12:21

2 Answers2

0

There are a few conceptual misunderstandings that I want to tackle in your code.

In modern React, your components should typically render some type of jsx, which is how React renders html. In your first example, you are using App to return your genres to your Screen component, which you don't need to do.

If your goal is to fetch some genres and then ultimately print them out onto the screen, you only need one component. Inside that component, you will useEffect to call an asynchronous function that will then await the api data and set it to a react state. That state will then be what you can iterate through.

When genres is first rendered by react on line 6, it will be undefined. Then, once the api data is retrieved, React will update the value of genre to be your array of genres which will cause the component to be re-rendered.

{genres && genres.map((genre) ... on line 20 checks to see if genres is defined, and only if it is, will it map (like looping) through the genres. At first, since genres is undefined, nothing will print and no errors will be thrown. After the genres are set in our useEffect hook, genres will now be an array and we can therefore loop through them.

Here is a working example of your code.

import React, { useState, useEffect } from "react";

import { getGenres } from "./api/functions";

function App() {
  const [genres, setGenres] = useState();

  useEffect(() => {
    async function apiCall() {
      const apiResponse = await getGenres("tv");
      console.log(apiResponse);
      setGenres(apiResponse);
    }
    apiCall();
  }, []);

  return (
    <div>
      <h1 className="text-3xl font-bold underline">H1</h1>
      {genres && genres.map((genre) => <div key={genre}>{genre}</div>)}
    </div>
  );
}

export default App;
RyanY
  • 635
  • 1
  • 8
  • 20
  • This looks similar to what OP is already doing. OPs problem is that `data` is `undefined` (in your case, that's `genres`), while this code works, it doesn't seem to explain why OPs code doesn't work. – Nick Parsons May 03 '22 at 12:03
0

You should use a combination or useEffect and useState

You should use useEffect to launch the async get, without launching it each rerendering. If a async function is used to get some data from outside the component, this is called 'side effect' so use useEffect.

You should use useState to react to changes on theses side effects, re-rendering the component to get the data in the dom.

In the next example, Im using a dummy async function getGenres which returns an array of genres.

Here is an example and a WORKING EXAMPLE :

const {useState, useEffect} = React;


async function getGenres() {

    var promise = new Promise(function(resolve, reject) {
     window.setTimeout(function() {
       resolve( ['genre1', 'genre2']);
     });
   });
   return promise;
   
}

const Screen = () => {
  const [genres, setGenres] = useState([])

  useEffect(
     () => {
        getGenres().then(
        res => setGenres(res)
      )
    }, [getGenres]
  )
  
  
  return (
    <ul>
      {
      genres.map(
        i => <li>{i}</li>
      )
      }
    </ul>
  );
}


const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<Screen/>)
Alfonso Tienda
  • 3,442
  • 1
  • 19
  • 34
  • This is what OP is already doing in their second example. Note that the `useEffect()` shouldn't be `async`, as it is expected to return a cleanup function that can be called (and not a Promise, which is what the `async` function implicitly returns), that's typically why an inner `async` funtion is used, as seen in OPs code. – Nick Parsons May 03 '22 at 11:57
  • There's no need for async useEffect, you're right. there's no need for a cleanup function neither in this example, this will be useful to cancel the settimeout or a request. Don't know what OP means. – Alfonso Tienda May 03 '22 at 12:15