2

I'm still new to ReactJS and I'm currently fiddling around with Spotify API and it works great but I stumbled into an issue whilst trying to render an array of images that were retrieved via the API. At first, I tried calling it inside the render() component but it only produces a list of empty 'undefined' images with only the alt attribute showing up. Secondly, I tried it inside return()and just has the same symptom as the former, this time without any of the images at all.

This issue only occurs however when I try using the .map() method; when I render an image individually, it works just fine.

import React, { Component } from "react";

let someData = "hello";

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      playlists: { name: [], plist_id: [], albumArt: [] }
    };
  }

  getPlaylists(someData) {
    let plistNames = [];
    let plistContents = [];
    let plistArts = [];

    someApi
      .searchPlaylists(someData)
      .then(data => {
        if (data.playlists.items) {
          if (data.playlists.items.length > 0) {
            console.log(data);
            data.playlists.items.forEach(plist => {
              plistNames.push(plist.name);
              plistContents.push(plist.id);
              plistArts.push(plist.images[0].url);

              this.setState({
                playlists: {
                  name: plistNames,
                  plist_id: plistContents,
                  albumArt: plistArts
                }
              });
            });
          }
        }
      })
      .catch(err => {
        console.error(err);
      });
  }

  render() {
    //Produces a list of empty images with only the alt attribute showing up
    let _playlists = this.state.playlists.albumArt.map((plist, index) => {
      return <img key={index} src={plist.albumArt} alt="Album Art"/>
    })
  return (
      <div className="App">
        <button onClick={() => this.getPlaylists(someData)}>
          Get Playlist
        </button>

      {_playlists}

      {/*  THIS PRODUCES a similar sympton as the former
          {playlists.albumArt.map((plist, index) => {
              <img key={index}  src={plist.albumArt} alt="Album Art"/>
              })
          } */}

      {/*  THIS IMAGE RENDERS THE FIRST ELEMENT FROM THE ARRAY AND WORKS FINE
            <img src={playlists.albumArt[0]}/> */}

      </div>
    );
  }
}

How does one tackle an issue like this? Thanks a mil for your time and help.

1
  • Apologies for being naive but wouldn't the <img key={index} src={plist.albumArt} alt="Album Art"/> suffice as it's already in an image tag? Commented Mar 13, 2019 at 20:32

4 Answers 4

1

You are currently mapping over your array of album art using:

let _playlists = this.state.playlists.albumArt.map((plist, index) => {
  return <img key={index} src={plist.albumArt} alt="Album Art"/>
})

The issue is that your setting the src of your <img /> tags to plist.albumArt when in fact the value of ‘plist’ is the url you want:

let _playlists = this.state.playlists.albumArt.map((plist, index) => {
  return <img key={index} src={plist} alt="Album Art"/>
})

As others have mentioned, it is also not recommended to be setting state during a loop. Rather loop/map through the data and store it in a local variable which you can use to call setState once.

Sign up to request clarification or add additional context in comments.

3 Comments

AHHHHHH dammit! What a rookie mistake! Thanks for pointing it out and it worked!!! I can't believe that's all there is to it. Cheers Pineda!
@madLad No worries at all, glad I could help.Question: why not store the playlists as an array of playlist object instead of having each field stored as separate arrays?
Seeing that each field contains distinctive contents, I assumed it was the best way to do it. Aye, I will do that, maxwell's suggestion surely gave me an insight on how to work around it. Thanks once again for the help!
1

What about a conditional inline statement? That way it will render after it receives the information?

    {this.state.playlist.name ?  
    //have your code here to display
    :
    <LoadingSpinner />
}

1 Comment

I appreciate the help and I like the idea of a loading spinner but this brings me similar symptoms on the methods I've used respectively i.e. produces a list of empty 'undefined' images and etc... which means the information is being received considering it does not load the spinner?
1

I believe the difference between your two cases is that in the "working" one, you don't have an alt prop.

You have to cut some corners if you want images to display cleanly during load. Either use modules that progressively handle image loading for you, or, if you don't care about accessibility, make the alt tag empty and give the image a default background color (if you want you can set the alt tag after loading).

Comments

1

I can't say for sure because I would need to see the structure of the data being returned but I'm fairly confident you do not want to be setting state within the forEach() loop a bunch of times.

Instead you probably want to map over the results of the data and transform it into some useful array of objects before adding it to state so that you can map over it in the render ( which looks fine ) and access the contents predictably.

That being said I would expect your .then() block after searchPlaylists call to look more like:

const playlistsWrangled =  data.playlists.items.map(plist => ({ 
  name: plist.name, 
  plist_id: plist.id, 
  albumArt: plist.images[0].url 
}));

this.setState({ playlists: wrangledPlaylists });

1 Comment

Lovely tip maxwell! I'll surely take note of it and give it a shot myself!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.