Search code examples
javascriptreactjsaxiosuse-effectuse-state

How do I use the output of one axios request as a dependency for another when rendering components in React?


I have been struggling with this for some time and I am not sure how to solve the issue.

Basically, I am trying to render some components onto my Index page, this is my code below:

App.js

import Index from "./Components/Index"
import axios from "axios"

export default function App() {
    const [movieList, setMovieList] = React.useState([])
    let featured = []
    let coming = []
    let showing = []

    React.useEffect(() => {
        console.log("Ran App Effects")
        axios.get(`API_CALL_TO_GET_LIST_OF_MOVIES`)
        .then(res =>{
            setMovieList(res.data)
        })
    }, [])

 
    return(
        <div>
             {movieList.map(movie =>{
            if(movie.status === 'featured'){
                featured.push(movie.api_ID)
            } else if (movie.status === 'upcoming'){
                coming.push(movie.api_ID)
            } else{
                showing.push(movie.api_ID)
            }
            })}
        
        <Index featured={featured} coming={coming} showing={showing}/>
        </div>
        
           
    )
}

In the code above I am receiving an array of Objects and based on what is in their status I am putting them in some empty arrays and sending them as props into my Index component. This is what my index component looks like:

import React from "react"
import Header from "./Header"
import Footer from "./Footer"
import MovieCard from "./MovieCard"
import axios from "axios"

export default function Index(props) {
    const [featuredMovies, setFeaturedMovies] = React.useState([])
    const [comingMovies, setComingMovies] = React.useState([])
    //const featured = [419704,338762,495764,38700,454626,475557]
    //const coming = [400160,514847,556678,508439,524047,572751]
    


    React.useEffect(() => {
        console.log("Ran Effect")

        axios.all(props.featured.map(l => axios.get(`API_CALL_TO_GET_SPECIFIC_MOVIE/${l}`)))
        .then(axios.spread(function (...res){
            setFeaturedMovies(res)
        }))
        .catch((err) => console.log(err))

        axios.all(props.coming.map(l => axios.get(`API_CALL_TO_GET_SPECIFIC_MOVIE/${l}`)))
        .then(axios.spread(function (...res){
            setComingMovies(res)
        }))
        .catch((err) => console.log(err))

    }, []) 


    return(
        <body>
            <Header />
            <section className="home">
                <div className="container">
                    <div className="row">
                        <div className="col-12">
                            <a className="home__title">FEATURED MOVIES</a>
                        </div>
                        
                        { featuredMovies.map(movie =>{
                return <MovieCard movie={movie} featured={true} />
                        }) }
                        {console.log(props.featured)}

                    </div>     
                </div>
            </section>


            <section className="home">
                <div className="container">
                    <div className="row">
                        <div className="col-12">
                            <a className="home__title">COMING SOON</a>
                        </div>
                        { comingMovies.map(movie =>{
                return <MovieCard movie={movie} featured={false} />
                        })}
                    </div>      
                </div>
            </section>
            
            <Footer/>
        </body>
    )
}

The issue I am running into is, whenever I run the app for the first time it works fine but then when I hit the refresh button the components do not render anymore

The only time it re-renders when I refresh the page is when I uncomment,

//const featured = [419704,338762,495764,38700,454626,475557]
//const coming = [400160,514847,556678,508439,524047,572751]

and replace the props.featured.map and props.coming.map with featured.map and coming.map hence using the hard coded values and not the values passed in from the props.

Any help with this would be much appreciated as I am completely stuck and currently pulling my hair out.


Solution

  • I took the liberty to tinker with your code. In the example below I've rearranged the data into three sets with the help of useMemo and by checking the status property of each movie. It is important to keep any data related logic outside of the render logic.

    I also moved around some of your HTML structure. You were outputting a <body> tag inside of a <div>. The outer layer should be in control of the outer HTML structure, so I moved that HTML to the App component.

    import { useState, useEffect, useMemo } from 'react'
    import Header from "./Components/Header"
    import Footer from "./Components/Footer"
    import Index from "./Components/Index"
    import axios from "axios"
    
    export default function App() {
      const [movieList, setMovieList] = useState([])
    
      const featuredMovies = useMemo(() => {
        return movieList.filter(({ status }) => status === 'featured');
      }, [movieList]);
    
      const upcomingMovies = useMemo(() => {
        return movieList.filter(({ status }) => status === 'upcoming');
      }, [movieList]);
    
      const showingMovies = useMemo(() => {
        return movieList.filter(({ status }) => status !== 'featured' && status !== 'upcoming');
      }, [movieList]);
    
      useEffect(() => {
        axios.get(`API_CALL_TO_GET_LIST_OF_MOVIES`)
          .then(res =>{
            setMovieList(res.data)
          })
      }, [])
    
      return (
        <body>
          <Header />
    
          <Index data={featuredMovies} title="Featured Movies" featured={true} />
          <Index data={upcomingMovies} title="Coming Soon" />
          <Index data={showingMovies} title="Showing Now" />
    
          <Footer/>
        </body>     
      )
    }
    

    Since we now have three sets of movies (featured, upcoming, and playing) it would also make sense to have three components that handle those data sets instead of having one that handles multiple. Each Index component gets its own data set and other props to render the movies within it.

    import MovieCard from "./MovieCard"
    
    export default function Index({ data, title, featured = false }) {
      return (
        <section className="home">
          <div className="container">
            <div className="row">
              <div className="col-12">
                <a className="home__title">{title}</a>
              </div>
              
              {data.map(movie => {
                return <MovieCard movie={movie} featured={featured} />
              })}
            </div>     
          </div>
        </section>
      );
    }