0

This has probably been asked a gazillion times before and I've looked at a hundred different places and tried a 100 different ways with more and less useEffects and returns here and there but I just can't fix it!

I'm trying to display stuff from a list of movies but cannot get it to show. I can see in logs that I do have the stuff i need in "movies" in the second useEffect but it doesnt produce any elements, I only get the wrapping div.

My code rn:

const SideScroller = ({scrollerName, titles}) => {
    const [movies, setMovies] = useState([]);
    const [content, setContent] = useState();

    useEffect(() => {
        let moviesTemp = [];
        
        titles.forEach(title => {
            async function fetchData() {
                const movie = await getMovie(title);
                moviesTemp.push(movie);
            }
            fetchData();
        });
        setMovies(moviesTemp);
    }, [titles])
   
    useEffect(() => {
        console.log(movies)
        setContent(() => {
            return (
                movies.map((movie) => {
                    console.log(movie);
                    return (
                        <div className='scrollerItem'>
                            <img src={movie.Poster} alt={movie.Title}  />
                            <p>{movie.Title}</p>
                        </div>
                    );
                })
            );
        });
    }, [movies])

    return (
        <div className='sideScroller'>
            {content}
        </div>
    )
}

3 Answers3

0

Try Like This

setContent(() => {
movies.map((movie) => {
console.log(movie);
return (
<div className='scrollerItem'>
<img src={movie.Poster} alt={movie.Title}  />
<p>{movie.Title}</p>
</div>
);
})
});
0

You're dropping the promise from the forEach on the floor and not awaiting it. This is actually the same as the famous How do I return the response from an asynchronous call?

    useEffect(() => {
        let moviesTemp = [];
        
        const fetch = async () => {
         await Promise.all(titles.map(title => {
            async function fetchData() {
                const movie = await getMovie(title);
                moviesTemp.push(movie);
            }
            return fetchData();
         }));
         setMovies(moviesTemp);
        }
        fetch();
    }, [titles])

Then I rewrote it a little bit for fun (not because it's necessarily any "better" than the otherway):

    useEffect(() => {
        const moviesTemp = [];
        const fetch = async (title) => moviesTemp.push(await getMovie(title))
        const fetchAll = async () => {
          await Promise.all(titles.map(fetch)); 
          setMovies(moviesTemp);
        }
        fetchAll();
    }, [titles])

EDIT: To address your second question (in the comments), it's because you've mutated state.

It's a very curious thing, but let me try to explain:

    useEffect(() => {
        let moviesTemp = [];
        
        titles.forEach(title => {
            async function fetchData() {
                const movie = await getMovie(title);
                moviesTemp.push(movie);
            }
            fetchData();
        });
        setMovies(moviesTemp);
    }, [titles])

You immediately (because the forEach is synchronous regardless of the fact it is executing an async function on each iteration) have set the new state to moviesTemp, but then, you asynchronously modified moviesTemp which is the exact same as mutating state. If you mutate stuff in react, it doesn't work.

Also, you shouldn't have a second effect, this is what your full and final component should look like:

const SideScroller = ({scrollerName, titles}) => {
    const [movies, setMovies] = useState([]);

    useEffect(() => {
        const fetchAll = async () => setMovies(
          await Promise.all(titles.map(getMovie))
        );
        fetchAll();
    }, [titles])


    return (
        <div className='sideScroller'>
           {movies.map((movie) => {
                    return (
                        <div className='scrollerItem'>
                            <img src={movie.Poster} alt={movie.Title}  />
                            <p>{movie.Title}</p>
                        </div>
                    );
                })
            }
        </div>
    )
}
Adam Jenkins
  • 51,445
  • 11
  • 72
  • 100
  • await without async? – Mario Vernari May 19 '21 at 12:35
  • This seems reasonable but to me the problem is really that in the _second_ useEffect the console logs prove I have the list of movies but it doesnt produce any elements on screen. – Greger Sundvall May 19 '21 at 14:57
  • @GregerSundvall - Yeah, I didn't pay attention to your second effect. I know exactly why you are observing that behavior, because you are mutating state, but not in the standard way that most people mutate state. Your shouldn't even have a second effect, see my edit. – Adam Jenkins May 19 '21 at 16:01
  • Thanks, this last version did it! – Greger Sundvall May 20 '21 at 08:16
0

Try this:

const SideScroller = ({scrollerName, titles}) => {
    const [movies, setMovies] = useState([]);

    useEffect(() => {
        async function fetchData() {
            let moviesTemp = [];
            for (const title of titles) {
                const movie = await getMovie(title);
                moviesTemp.push(movie);
            }
            setMovies(moviesTemp);
        };
        fetchData();
    }, [titles])
   
    return (
        <div className='sideScroller'>
            {movies.map((movie) => {
                    console.log(movie);
                    return (
                        <div className='scrollerItem'>
                            <img src={movie.Poster} alt={movie.Title}  />
                            <p>{movie.Title}</p>
                        </div>
                    );
                })}
        </div>
    )
}
Mario Vernari
  • 6,649
  • 1
  • 32
  • 44
  • Should there be some kind of null check for the first render where movies isnt filled? Or is it enough that that movies default to []? Anyway I still didnt get any child elements... – Greger Sundvall May 19 '21 at 15:05
  • unless you set a non-array, the initial value and the subsequent `moviesTemp` are arrays, so there's no problem. I don't know how is your complete code, but here is a prototype using fake load and no images: [link](https://codesandbox.io/s/silent-water-84c1z?file=/src/App.tsx) – Mario Vernari May 19 '21 at 16:20