I'm building a movies app in React and I'm using TMDB's api for that. I'm completely new to functional components and have a few queries. First I fetch all the genres using an api, and then iterate the genres one by one and call another API for fetching 20 movies in that genre.
The final data should look like this:
[{
genre: 'action',
movies: [],
},
{
genre: 'adventure',
movies: [],
}]
So that I can iterate the above array of objects and display the genre and its corresponding movies.
Right now, I'm stuck with the below code.
const [movieData, setMovieData] = useState([]);
const fetchGenres = async () => {
const url = `https://api.themoviedb.org/3/genre/movie/list?api_key=xxx&language=en-US`;
try {
const res = await fetch(url);
const data = await res.json();
setMovieData(data.genres);
} catch (err) {
console.error(err);
}
}
Now, coming to the queries,
Solutions are much appreciated!
To my mind it's better to keep your data separately. I mean array of genres and array of movies. So you will have:
const [genres, setGenres] = useState([]);
const [movies, setMovies] = useState([]);
then you make your async call to fetch genres and set it to state:
const fetchGenres = async () => {
const url = `https://baseurl/3/genre/movie/list?api_key=token&language=en-US`;
try {
const res = await fetch(url);
const data = await res.json();
setGenres(data.genres);
} catch (err) {
console.error(err);
}
};
React.useEffect(()=>{
fetchGenres();
},[]);
to find out that the genres were set you can create an effect, that will update each time when genres change:
async function fetchMovies() {
let result = [];
for (const genre of genres) {
const url = `https://baseurl/3/discover/movie?api_key=token&with_genres=${genre.id}`;
const res = await fetch(url);
const { results } = await res.json();
result = [...result, ...results];
}
setMovies(result);
}
React.useEffect(() => {
fetchMovies();
}, [genres]);
and finally we can use React.useMemo to calculate the result array we need according to genres and movies, it will be recalculated each time when one of the inner params change:
const resultArray = React.useMemo(() => {
return genres.map(genre => ({
genre,
movies: movies.filter((movie) => movie.genre_ids.includes(genre.id))
}));
}, [genres, movies])
I believe you will get an array like this
[{
genre: 'action',
movies: [],
},
{
genre: 'adventure',
movies: [],
}]
As you already said, you first need to fetch the array of genres. Then you must iterate over this array and create a list of promises to fetch the corresponding movies. Once theses promises have been resolved (using Promise.all function) you can update the state variable movieData.
const GENRE_URL = `https://.../genre/movie/list?api_key=xxx&language=en-US`;
const MOVIE_URL = `https://.../movie/list?api_key=xxx&language=en-US&genre=`;
async function fetchGenres() {
try {
const genres = await fetch(GENRE_URL).then(res => res.json());
const moviePromises = genres.map(genre => {
return fetch(MOVIE_URL + genre)
.then(res => res.json())
// build an object containing 2 keys `genre` and `movies`
.then(movies => ({genre, movies}));
});
return await Promise.all(moviePromises);
} catch (err) {
console.error(err);
throw err;
}
}
you must adapt the MOVIE_URL to your needs, I don"t really know what the real url is, I imagine you need to pass the genre value as a path param or a query param.
you can now call this function in the useEffect hook to load the movies when the component is rendered.
function App() {
const [movieData, setMovieData] = useState([]);
useEffect(() => {
fetchMoviesGroupByGenre()
.then(data => setMovieData(data))
.catch(...);
}, []);
return ...
}
In term of performance, you will not display anything before all the movies have been fetched.
If it's a problem for you, you can update the state variable as soon as the api gives you an answer
async function fetchGenres() {
try {
const genres = await fetch(GENRE_URL).then(res => res.json());
const moviePromises = genres.map(genre => {
return fetch(MOVIE_URL + genre)
.then(res => res.json())
.then(movies => {
const item = {genre, movies};
setMovieData(prevMovieData => [...prevMovieData, item]);
return item;
]);
});
return await Promise.all(moviePromises);
} catch (err) {
console.error(err);
throw err;
}
}
From what I understood what you want to do is
setMovieData(prevMovieData => ([
...prevMovieData,
{ genre: genreFromApiCall, movies: moviesFromApiCall }
])
)