1

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;

3 Answers 3

2

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.

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

5 Comments

Thank you. I think this may be the way to go. How would I create an array with the full movie information? I would have to get the imdbID number on the first call and then make a second call with it? Would this be done in the await call or a for loop?
It's just fetch()
Hi. I've edited my post with the response to your comment. I didn't know you were using fetch. Yes, you'd need to await. Google async for loops.
Hi, thank you again. I'm going to try this.
Awesome, so glad.
1

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

2 Comments

Thank you for your response.I'm looking at this. I'm not using the button anymore so I'm not sure what has to happen. I am making two calls. The first is to get the movies themselves with title, poster, year and the imdbID. My question is how to make the 2nd call to get the more detailed information.
Updated my answer a bit, take a look please, logic is pretty simple, not much changes needed in your code. But it would be better if you extend your API to provide you all the information you needed in case you really need it in place for all the items.
0

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

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.