Search code examples
javascriptreactjsreact-hooksreact-router-domuse-effect

Having trouble with using UseEffect in place of componentDidMount and componentDidUpdate


I'm trying to apply useEffect as you would use componentDidMount and componentDidUpdate in a class component.So, I have a component called SeasonPage and inside this component are links to the same component with different params. However, when the url changes its params the component does not re-render the functions I'm calling in useEffect. At first I had an empty dependency array, but after some research I added props.match.params.seasonid but still no luck.I was hoping someone can show the proper way to use hooks to do this task. Thanks,

export default function SeasonPage(props) {

    const [season, setSeason] = useState({});
    const [seasonID, setSeasonID] = useState(props.match.params.seasonid);
    const [showID] = useState(props.match.params.id);
    const [episodes, setEpisodes] = useState([]);
    const [show, setShow] = useState({});
    const [otherSeasons, setOtherSeasons] = useState([]);


useEffect(() => {
     getShow();
     getSeasons();
     console.log(props);
     
  },[props.match.params.seasonid]);

  const getShow = () =>{
      axios.get(`https://api.themoviedb.org/3/tv/${showID}?api_key=apikey&language=en-US`)
      .then(results => {
          setShow(results.data)
          setOtherSeasons(results.data.seasons)
      })
      .catch(error =>{
          console.log(error)
      });
  }

const getSeasons = () =>{
    axios.get(`https://api.themoviedb.org/3/tv/${showID}/season/${seasonID}?api_key=apikey&language=en-US`)
    .then(results =>{
        console.log(results);
        setSeason(results.data);
        setEpisodes(results.data.episodes)
    })
    .catch(error =>{
        console.log(error);
        
    });

};

const handleError = (e) =>{
    console.log(e);
    e.target.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/ac/No_image_available.svg/300px-No_image_available.svg.png';

}

let episodesjsx = episodes.map((ep, i) =>{
    return <MovieCard movie={ep} key={i} id={ep.id} title={ep.name} overview={ep.overview} voteAverage={ep.vote_average} backdropPath={ep.still_path} type="episode"/>;
});

let otherSeasonsjsx = otherSeasons.map(seasonx => {
    return seasonx.season_number !== parseInt(seasonID )&&  <SeasonsCard season={seasonx} tvshowID={show.id} />
});

    return (
        <div className="season-page">
            <div className="container">
                <div className="banner" style={{backgroundImage:`url("https://image.tmdb.org/t/p/original/${show.backdrop_path}")`}}>
                    <div className="banner-overlay">
                        <div className="banner-content">
                            <div className="banner-content-poster-div">
                            <img src={`https://image.tmdb.org/t/p/w342/${season.poster_path}`}></img>
                            </div>
                            <div className="banner-content-details">
                                <h1>{show.name}</h1>
                                <h3>{season.name}</h3>
                                <p>{season.air_date}</p>
                                <div className="overview">

                                <p>{season.overview}</p>
                                </div>
                            </div>
                        </div>
                        </div>
                </div>
       
                <div className="scroll-container-div">
                    <h2>{season.name} Episodes</h2>
                    <div className="scroll-div">
                        {episodesjsx}
                    </div>
                </div>
                <div className="scroll-container-div">
                    <h2>Other Seasons</h2>
                    <div className="scroll-div">
                        {otherSeasonsjsx}
                    </div>
                </div>
           
            </div>
        </div>
    )
}

Below is the component that has the button to link to the same component being rendered.



const useStyles = makeStyles({
  root: {
    maxWidth: 345,
    minWidth: 325,
    margin: '15px',
  },
  media: {   
      height: 250,        
  objectFit: 'cover',      
    
  },
});

export default function SeasonCard(props) {
  const classes = useStyles();
  const description = props.overview ? props.overview.slice(0, 120) + "...": '';
  let pic = `https://image.tmdb.org/t/p/original/${props.season.poster_path}`;
 

  return (
    <Card className={classes.root}>
      <CardActionArea>
        <CardMedia
        className={classes.media}
          component="img"
          alt={props.season.name}
          image={pic}
          title={props.season.name}
        />
        <CardContent>
          <Typography gutterBottom variant="h5" component="h2">
            {props.season.name} ({props.season.episode_count} ep.)
          </Typography>
          <Typography variant="body2" color="textSecondary" component="p">
            {description}
          </Typography>
        </CardContent>
      </CardActionArea>
      <CardActions>
        
        <Button size="small" color="primary" component={Link} to={`/tvshows/${props.tvshowID}/seasons/${props.season.season_number}`} style={{width: '100px'}}>
          Learn More
        </Button>
      </CardActions>
    </Card>
  );
}

and here is my router in case you need it.

    <Route path="/tvshows/:id/seasons/:seasonid" component={SeasonPage} ></Route>


Solution

  • I think these lines is not right. Synchronization of props with state is an anti-pattern. (More on this)

    const [seasonID, setSeasonID] = useState(props.match.params.seasonid);
    const [showID] = useState(props.match.params.id);
    

    Instead of that way, just take ID directly from props.

    useEffect(() => {
        getShow();
        getSeasons();
        console.log(props);
    
    }, [props.match.params]); //excecute code whenever id and/or seasonid changed
    
    const getShow = () => {
        const { id } = props.match.params;
    
        axios.get(`https://api.themoviedb.org/3/tv/${id}?api_key=apikey&language=en-US`)
            .then(results => {
                setShow(results.data)
                setOtherSeasons(results.data.seasons)
            })
            .catch(error => {
                console.log(error)
            });
    }
    
    const getSeasons = () => {
        const { id, seasonid } = props.match.params;
    
        axios.get(`https://api.themoviedb.org/3/tv/${id}/season/${seasonid}?api_key=apikey&language=en-US`)
            .then(results => {
                console.log(results);
                setSeason(results.data);
                setEpisodes(results.data.episodes)
            })
            .catch(error => {
                console.log(error);
    
            });
    
    };