Search code examples
reactjsasynchronousasync-awaitpromisefetch

How Do You Resolve Promises for Attributes of HTML Tags?


I'm trying to make a 'Favorite Albums' app using React, GraphQL, Apollo, MongoDB, and Express. I'm having trouble implementing a feature that automatically grabs cover art from a localhost server that queries the MusicBrainz API. So far, the art-fetching aspect works perfectly: if I enter an artist + album combination, I get the cover art and that album is returned to the client side.

My ideal flow is this:

  • When the app loads, get the albums data from MongoDB.
  • If an album has a thumbnail property (a URL to the cover art),
    • use the thumbnail and display the image.
  • Else,
    • go fetch the cover art.
    • Display the image once the promise is fulfilled.

The problem is, getAlbumCover(album) always returns a promise, as async functions seem to do. I'm just not sure what I have to do to make it return the URL from the fetch operation. Any help would be immensely appreciated.

Below is the code for the Album component.

import { useState } from 'react';

const Album = (props) => {

    const getAlbumCover = async (album) => {
        // Slugify text:
        function convertToSlug(Text) {
            return Text
                .toLowerCase()
                .replace(/ /g, '-')
                .replace(/\//g, '-')
                .replace(/[^\w-]+/g, '')
                ;
        }
        const artist = convertToSlug(album.artist.name);
        const name = convertToSlug(album.name);
        var result = await fetch(`http://localhost:5000/album-art/${artist}/${name}`, {
            method: 'get',
            headers: {
                'Accept': 'application/json'
            }
        })
            .then(res => res.json())
            .then((result) => {
                return (<img id={`img-${album.id}`} src={result} />);
            })
    }
    
    const album = props.album;
    return (
        <div className="album-container">
            <img id={`img-${album.id}`} src={album.thumbnail ? album.thumbnail : getAlbumCover(album)} />
            <div className="album-header">
                <h3><em>{album.name}</em> <span className="details-yor">({album.yearOfRelease})</span></h3>
                <h3>- {album.artist.name}</h3>
            </div>
        </div>
    )
}

export default Album;

Solution

  • If the cover needs to be retrieved, put it into state:

    const Album = (props) => {
        const [cover, setCover] = useState('');
    

    Get the cover, if needed, when the component mounts:

    useEffect(() => {
        if (!props.album.thumbnail) {
            getAlbumCover();
        }
    }, []);
    
    // inside getAlbumCover:
    .then(res => res.json())
    .then(setCover)
    .catch(handleError); // don't forget this; unhandled rejections should always be avoided
    

    Then render it:

    <img id={`img-${album.id}`} src={album.thumbnail || cover} />