Javascript : rendering HTML before Data is loaded from API call

Viewed 143

I am working on a small project, and I have a hard time reaching what I need. I've already requested some help on another matter about this project, but I'm at a loss again, I think this time because I've bitten a bit more than I could chew. So here goes : I'm using the Musicbrainz API to retrieve informations for a specific track using it's id (it's length, release date, artists, etc ...).

But I'm trying to also show below the details of the track the covers of all the releases where the track is present. This require new requests for each of the release. I manage to retrieve the URLs of the images that I need and push them into an array that I then map / join to add some <img> element so that it can render in the HTML.

However, my issue is that when I click on the button to show more details about a specific track, which in turn fires the function controlTrackDetail in my controller (I've tried to implement some basic MVC architecture as practice ...), the first part of the "rendering" (the general informations from TrackView.render) is fine, but the second part (the covers) is not. I gathered that when I call my CoverView.renderCovers method, the array used as a parameter is still empty, and as such, nothing happens. If I don't empty the array, and click again on my button, it does show all my covers, but only because the URL are those of the previous call to controlTrackDetail.

If anyone has any idea as to how I could tackle this, and only render the covers after all the request in the forEach loop unqReleaseCoversUrl(mbid.id) of API calls is done, that would help me plenty. Below, you'll find the "important" snippets of code to (hopefully) understand my issue.

GET_JSON function (race between a fetch and timeout)

export const GET_JSON = async function (url) {
  try {
    const res = await Promise.race([
      fetch(url),
      timeout(CONSTANTS.TIMEOUT_SEC),
    ]);
    const data = await res.json();

    if (!res.ok) throw new Error(`${data.message} (${res.status})`);
    return data;
  } catch (err) {
    throw err;
  }
};

The model part, where I create the loadTrackDetails function to recover the informations I need.

import { CONSTANTS } from "./config.js";
import { CONVERT_MILLIS_TO_MINS_SECONDS } from "./helpers.js";
import { GET_JSON } from "./helpers.js";
import { SHORTEN_STRING } from "./helpers.js";
import { CONSTRUCT_URL_PART } from "./helpers.js";
/*
https://musicbrainz.org/ws/2/recording/738920d3-c6e6-41c7-b504-57761bb625fd?inc=genres+artists+ratings+releases&fmt=json
loadTrackDetail("738920d3-c6e6-41c7-b504-57761bb625fd");
*/
export const details = {
  trackDetails: {},
  artistDetails: {},
  releaseDetails: {},
  coverUrlArray: [],
};

export const loadTrackDetail = async function (id) {
  try {
    const trackData = await GET_JSON(
      encodeURI(
        `${CONSTANTS.API_URL}${id}?inc=genres+artists+ratings+releases&fmt=json`
      )
    );

    details.trackDetails = {
      trackTitle: trackData.title ?? "No title provided",
      trackID: trackData.id,
      trackReleaseDate: trackData["first-release-date"] ?? "No date provided",
      trackLength: trackData.length
        ? CONVERT_MILLIS_TO_MINS_SECONDS(trackData.length)
        : "No duration provided",
      trackArtists: trackData["artist-credit"].length
        ? trackData["artist-credit"]
        : "No information on artists",
      trackReleasesBase: trackData["releases"].length
        ? trackData["releases"]
        : "No information on releases",
      trackReleasesCleanOne: trackData["releases"].length
        ? trackData["releases"].map((release) => ({
            id: release.id,
            title: release.title,
          }))
        : "No information on releases",

      trackGenres: trackData["genres"].length
        ? trackData["genres"]
        : "No information on genres",
      trackRating: trackData.rating.value ?? "No rating yet",
    };

    if (details.trackDetails.trackReleasesCleanOne.length > 0) {
      details.trackDetails.trackReleasesCleanOne.forEach((mbid) =>
        unqReleaseCoversUrl(mbid.id)
      );
    }
    details.coverUrlArray = details.coverUrlArray.filter(function (element) {
      return element !== undefined;
    });
    console.log(details.coverUrlArray);
  } catch (err) {
    throw err;
  }
};
/*

*/

export const unqReleaseCoversUrl = async function (mbid) {
  try {
    const coverData = await GET_JSON(
      encodeURI(`${CONSTANTS.COVER_API_URL}${mbid}`)
    );
    console.log(coverData.images);
    coverData.images.forEach((image) => {
      image.thumbnails["500"]
        ? details.coverUrlArray.push(image.thumbnails["500"])
        : null;
    });
  } catch (err) {
    throw err;
  }
};

And finally, the controller part, fired on click on a button

const controlTrackDetail = async function (trackID) {
  try {
    TrackView.renderSpinner();

    await detailsModel.loadTrackDetail(trackID);

    // 2) Rendering Recipe
    TrackView.render(detailsModel.details.trackDetails);

    CoverView.renderSpinner();

    CoverView.renderCovers(detailsModel.details.coverUrlArray);
  } catch (err) {
    console.log(err);
  }
};

Like I said, this works ... fine (though I guess it's not really clean ...), aside from the fact that I'm rendering my covers early, and I'm just not sure how to delay it until the URLs are pushed into the arrays.

Thanks for reading me !

PS : here are a few pictures to understand. On first click, what I see (nothing appears below Cover Arts, where the rendering of the covers should happen) : enter image description here

After a few milliseconds, the array is filled with the URLs I need : enter image description here

And when I go back to the results, and click on any button without emptying the array, it is used to render the covers using the URLs that I pushed the first time around (and this array will carry on growing !) enter image description here

0 Answers
Related