reactjsasync-await

How to await api calls in UseEffect in ReactJS?


I am using useEffect to make an api call, and within the resolution of the promise, I am making another api call, but I seem to have some async issues where objects are being created before its necessary parameters are being set. The console.log before creating my Song object is printed before the console.logs from within the resolution of the nested promises. How can I make it so my code executes in the correct order?

class Song {
    constructor(id, title, artists, genres) {
        this.id = id;
        this.title = title;
        this.artists = artists;
        this.genres = genres;
    }
}

export default function Dashboard({ code }) {
    const accessToken = useAuth(code);
    const [songs, setSongs] = useState([]);
    console.log(songs);

    useEffect(() => {
        var genres;
        const fetchArtistGenres = async (artistId) => {
            await spotifyApi.getArtist(artistId).then((res) => {
                console.log("GENRES FROM FETCH FUNCTION", res.body.genres);
                return res.body.genres;
            });
        };
        if (accessToken) {
            spotifyApi.getMySavedTracks({ limit: 2 }).then((res) => {
                setSongs(
                    res.body.items.map((item) => {
                        const artists = item.track.artists.map((artist) => {
                            return {
                                name: artist.name,
                                id: artist.id,
                            };
                        });

                        if (!item.track.genres) {
                            // make call to artist endpoint
                            fetchArtistGenres(artists[0].id)
                                .then((res) => {
                                    genres = res;
                                    console.log("GENRES FROM PROMISE", res);
                                })
                                .catch((err) => {
                                    console.log(err);
                                });
                        } else {
                            genres = item.track.genres;
                        }
                        console.log("genres before creating song: " + genres);

                        return new Song(
                            item.track.id,
                            item.track.name,
                            artists,
                            genres
                        );
                    })
                );
            });
        }
    }, [accessToken]);

Solution

  • I think the problem lies with the async fetch of genres as the map iterator will not wait for the api call to return before continuing.

    I would suggest changing res.body.items.map to a for loop and using the await operator with your fetchArtistGenres call. This will pause the loop while it waits for the artist genres to be returned.

    Await can be used in map iterators but it does not pause the loop, it will instead return a promise, your code would require a lot of refactoring to support this approach.

    I found this article good for understanding async calls in loops.