Search code examples
reactjsreact-hooksuse-effect

The image is null, cannot read properties


const url = "http://api.tvmaze.com/search/shows?q=girls";

export default function MoviesList() {
    const [movie, setMovie] = useState([]);

    useEffect( () => {
        loadData();
        }, []);
        const loadData = async () => {
            await fetch(url)
            .then(response => response.json())
            .then(data => setMovie(data))
            .catch(error => console.log(error));
        }

    function movieCard (movie) {
        return (
            <Card className="movie-card" key={movie.show.id}>
                <Card.Body>
                    <Card.Title>{movie.show.name}</Card.Title>
                    <Card.Text>{movie.show.url}
                    <Card.Img src={movie.show.image.medium} />
                    </Card.Text>
                </Card.Body>
            </Card>
        );
    }
    return (
        <>
        <h1>Movie List</h1>
        {movie.map(movieCard)}
        </>
    )
}

So what I'm trying to do is I try to access the image URL from the API and use it on my card component. But as soon as I access it, the web goes blank and this is what shows up at console.

Console log error

Any idea how to fix this? Thanks a lot in advance!

enter image description here


Solution

  • When it comes to fetching with APIs you need to be ready for larger data structures, since I can only see 1 item in the output above I'll try and show why things are not working.

    In your setMovie, you set movie to the entirety of the data.

    If we instead go ahead and set movie by using setMovie(data[0]) we instead save that first item.

    There might be some future adjustments, but now movie will have access to both url, name and medium in your movieCard function.

    Ask yourself if it really is data[0] that you need, or if your initial fetch(url) needs to be altered to get 1 specific item.


    Based on your comment below, you want to show movieCards for each item. How I would do that would rewrite abit and make a return that calls movieCard for each movie and then returns that.

    const [movie, setMovie] = useState([]);

    I would change to const [movies, setMovies] = useState([]); to show that we might be working with several movies depending on what we fetch.

    We also go back to your original way of setting the state: setMovie(data) as we now know that you have all items in there.

    What we can do next is rewrite your return like this:

     return (
            <>
            <h1>Movie List</h1>
            {movies.map((movie) => (
               movieCard(movie);
            )}
            </>
        )
    

    What will happen now is that instead of returning 1 item calling movieCard function, the state containing all the data will iterate and create a movieCard for each movie item it contains.