Home > Enterprise >  Nesting API calls in React/JavaScript maybe call 2nd api call in map()
Nesting API calls in React/JavaScript maybe call 2nd api call in map()

Time:07-10

I have a tricky question in React. I was able to make an API call to get the movies but now I need to make an API call with just the ID of one movie to get the rest of the information.

I'm tempted to make an API call inside the map() function as it will return to me unique information. If I try to make a separate call on its own with the fetchMovieInfo, it will update all the movies with the same information.

I feel the solution is to make another API inside the map() function.

You could see in the comment I was trying to call an API based on a button, but as I said, it updated all the cards with the same information.

Any help is greatly appreciated as I'm almost there...

import { useState } from "react";

const Home = () => {
  const key = 'eee0805f';
  const baseUrl = 'https://www.omdbapi.com/?apikey='   key;
 
  // This is the search input
  const [message, setMessage] = useState('');
  const [searchTerm , setSearchTerm] = useState('');
  const [movies, setMovies] = useState(null);
  const [movieInfo, setMovieInfo] = useState(null);

  const handleChange = event => {
    setMessage(event.target.value);
  };

  const fetchData = async (url) => {
    try {
        const response = await fetch(url);
        const json = await response.json();
        const newMovies = json.Search;
        setMovies(newMovies);
    } catch (error) {
        console.log("error", error);
    }
  };

  const fetchMovieInfo = async (url) => {
    try {
        const response = await fetch(url);
        const json = await response.json();
        const newInfo = json;
        setMovieInfo(newInfo);
    } catch (error) {
        console.log("error", error);
    }
  };

  const searchMovie = () => {
    let url = baseUrl   '&s='   encodeURIComponent(message);
    console.log(url);
    fetchData(url);

    setSearchTerm(message);
    setMessage("");
  }

  const moreInfo = (e) => {
    let url = baseUrl   '&i='   e.target.getAttribute('data-key');
    console.log(url);
    fetchMovieInfo(url);

  }

  // console.log(movies);
  console.log(movieInfo);

  return (
    <div className="home">
      <div className="searchContainer">
        <input type="text" id="message" name="message" onChange={handleChange} value={message} placeholder="Please Enter Movie" />
        <span style={{ 
          color: 'white', 
          backgroundColor: '#f1356d',
          borderRadius: '8px' ,
          padding: '2px 8px'
        }} onClick={searchMovie}>Search</span>
      </div>

      <h3>Title Searched: {searchTerm}</h3>

      {movies && <div>
        {movies.map(movie => (
          <div className="movie-preview" key={movie.imdbID} >
            <div className="movie-content">
              <div className="movie-premier-image-content">
                <img className="movie-preview-image" src={movie.Poster} />
              </div>
              <div>
                <h2 className="movie-preview-headline">{ movie.Title }</h2>
                <p>{movie.Year}</p>

                  {/* The problem here is that the information is all the same.  */}
                  {movieInfo && <div>
                    <p>Director(s): {movieInfo.Director}</p>
                    <p>Genre: { movieInfo.Genre }</p>
                    <p>Date released: {movieInfo.Released}</p>
                  </div>}


              </div>
            </div>

            {/* <div style={{ 
              color: 'white', 
              backgroundColor: '#f1356d',
              borderRadius: '8px' ,
              padding: '2px 8px',
              height: '24px',
              width: '75px'
              }} onClick={moreInfo} data-key={movie.imdbID}>More Info</div> */}
          </div>
        ))}


      </div>}



    </div>
  );
}
 
export default Home;

CodePudding user response:

In my opinion, it's bad practice to make a call in the DOM.

I would create an array to store the movies with full info and then run a for-each loop to go over every movie from your initial call and run a second call on it to get more information and append the result to the array made at the start.

Then map THIS array to the movie element in the DOM.

However, if you're not bothered about changing the call, you might as-well include all the info on the initial call to save yourself making many calls to the API.

Hope this helps.

EDIT: In response to your comment, I'm not sure how the API you are using works. However, if you wanted to loop through, you could do:

const movies = getAllMovies() // run the api call to get the movies

let fullInfoMovies = [];

movies.forEach((movie) => {
     const fullInfo = getMovieInfo(movie.id); // Run the call to get your movie info of the ID of the movie currently being ran and assign it to a variable for use.
     fullInfoMovies.push(fullInfo);
});

As I say, I have not used the API you are using but this should help you figure out how to build the structure for the use of the API.

Hope this helps.

CodePudding user response:

Update based on information from comment.

The easiest way for you would be to extend movies array objects with moreInfo field.

// const [movieInfo, setMovieInfo] = useState(null); // delete it, i guess you dont need it anymore

const fetchMovieInfo = async (movie) => {
    try {
        let url = baseUrl   '&i='   movie.imdbID;
        const response = await fetch(url);
        return await response.json();
    } catch (error) {
        console.log("error", error);
    }
};

const fetchData = async (url) => {
    try {
        const response = await fetch(url);
        const json = await response.json();
        const newMovies = json.Search;
        const moviesWithInfoPromises = newMovies.map(m => {
            return fetchMovieInfo(m).then(info => m.movieInfo = info);
        });
        await Promise.all(moviesWithInfoPromises );
        setMovies(newMovies);
    } catch (error) {
        console.log("error", error);
    }
};

and

{movie.movieInfo && <div>
    <p>Director(s): {movie.movieInfo.Director}</p>
    <p>Genre: { movie.movieInfo.Genre }</p>
    <p>Date released: {movie.movieInfo.Released}</p>
</div>}

CodePudding user response:

In my experience when there's an array (so we are indexing elements by order) but i'm trying to identify elements by their identifier key (maybe id) I keep a spare copy to keep track of the changes indexed by their identifier. But also allows me to retrieve data as needed without retrieving ALL of the movies info. This only works if you are not displaying a thousand elements for performance reasons, be wary. But maybe it can suit your needs to avoid some following calls to api if you already retrieved info for a specific movie and the user is going back and forth.

movies.reduce((acc, curr) => ({...acc, [curr.id]: {...curr, info: null}}), {})

Then you just update movies[id].info as needed and do not have to retrieve a second time if the needs to redisplay arises.

Your "more info" box would then render only when open and !!movies[id].info

And you get to track whatever state relative to an item you need because everything is sorta namespaced under id

  • Related