1

We are trying to populate an array with songs using the Spotify API.

The API allows you to grab 50 songs at once, so we created a loop using an offset to get 250 songs.

After we get the songs, I want to update this.state.searchResults to hold the songs (with some filtering on the results).

But, when we use setState, thee array is not updated. I have done some research on this and I understand setState is async, and that the array is immutable but the solutions I found did not work.

In App.js:

import React, { Component } from "react";
import SearchBar from "../SearchBar/SearchBar";
import SearchResults from "../SearchResults/SearchResults";
import Playlist from "../Playlist/Playlist";
import "./App.css";
import Spotify from "../../util/Spotify";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      searchResults: [],
      playlistName: "New Playlist",
      playlistTracks: [],
    };

    this.addTrack = this.addTrack.bind(this);
    this.removeTrack = this.removeTrack.bind(this);
    this.savePlaylist = this.savePlaylist.bind(this);
    this.updatePlaylistName = this.updatePlaylistName.bind(this);
    this.search = this.search.bind(this);
  }

  componentDidMount() {
    window.addEventListener("load", () => {
      Spotify.getAccessToken();
    });
  }

  addTrack(track) {
    if (
      !this.state.playlistTracks.find(
        (playlistTrack) => playlistTrack.id === track.id
      )
    ) {
      this.setState((prevState) => ({
        playlistTracks: [...prevState.playlistTracks, track],
      }));
    }
  }

  removeTrack(track) {
    this.setState({
      playlistTracks: this.state.playlistTracks.filter(
        (playlistTrack) => playlistTrack.id !== track.id
      ),
    });
  }

  updatePlaylistName(name) {
    this.setState({ playlistName: name });
  }

  savePlaylist() {
    const trackUris = this.state.playlistTracks.map(
      (playlistTrack) => playlistTrack.uri
    );
    Spotify.savePlaylist(this.state.playlistName, trackUris).then(() => {
      this.setState({ playlistName: "another new one", playlistTracks: [] });
    });

  }



  search(term) {
    let temp = [];
    for (let i = 0; i < 5; i++) {
      Spotify.search(term, (i * 50).toString()).then((response) => {
        response.forEach(function (arrayTrack) {
          temp.push(arrayTrack);
        });
      });
    }

    

    this.setState((prevState) => ({
      searchResults: [...prevState.searchResults, ...temp],
    }));

    console.log("temp= ", temp);
    console.log("searhResults= ", this.state.searchResults);
  }

  render() {
    console.log("this.state.searchresults:", this.state.searchResults);
    return (
      <div>
        <h1>
          Ja<span className="highlight">mmm</span>ing
        </h1>
        <div className="App">
          <SearchBar onSearch={this.search} />

          <div className="App-playlist">
            <SearchResults
              onSearch={this.search}
              searchResults={this.state.searchResults}
              onAdd={this.addTrack}
            />
            <Playlist
              name={this.state.playlistName}
              tracks={this.state.playlistTracks}
              onRemove={this.removeTrack}
              onNameChange={this.updatePlaylistName}
              onSave={this.savePlaylist}
            />
          </div>
        </div>
      </div>
    );
  }
}

export default App;

In Spotify.js:

search(term, offset) {
    const accessToken = Spotify.getAccessToken();
    return fetch(
      `https://api.spotify.com/v1/search?type=track&q=year:${term}&limit=50&offset=${offset}`,
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      }
    )
      .then((response) => {
        if (response.ok) {
          return response.json();
        }
      })
      .then((jsonResponse) => {
        if (!jsonResponse.tracks) {
          return [];
        } else {
          var results = [];
          results = jsonResponse.tracks.items.map((track) => ({
            id: track.id,
            name: track.name,
            artist: track.artists[0].name,
            album: track.album.name,
            uri: track.uri,
            release_date: track.album.release_date,
            release_date_precisions: (track.album.release_date_precision =
              "month"),
          }));
          var filtered_results = [];
          results.forEach(function (arrayTrack) {
            if (arrayTrack.release_date.includes(`${term}-10`)) {
              filtered_results.push(arrayTrack);
            }
          });

          console.log(results);
          console.log("filtered results for i= ", filtered_results);
          return filtered_results;
        }
      });
  },

The console shows temp is populated correctly, but searchResults is empty:


App.js:95 
temp=  \[\]0: {id: '60a0Rd6pjrkxjPbaKzXjfq', name: 'In the End', artist: 'Linkin Park', album: 'Hybrid Theory (Bonus Edition)', uri: 'spotify:track:60a0Rd6pjrkxjPbaKzXjfq', …}
1: {id: '0I3q5fE6wg7LIfHGngUTnV', name: 'Ms. Jackson', artist: 'Outkast', album: 'Stankonia', uri: 'spotify:track:0I3q5fE6wg7LIfHGngUTnV', …}
2: {id: '3IV4swNduIRunHREK80owz', name: "Rollin' (Air Raid Vehicle)",
.......

App.js:96 
searhResults=  \[\]length: 0\[\[Prototype\]\]: Array(0)......

I have tried a variety of ways to update the array. The current method was taken from a website that discussed the immutability of the array.

The first thing I tried was this.setState({ searchResults: searchResults }); which of course doesn't work.

However, if you do the following:

   this.setState({ searchResults: temp }, () => {
     console.log(this.state.searchResults);
   });
   console.log(this.state.searchResults);

the first console.log will print searchResults populated correctly. But the second console.log return an empty array. I don't understand why that happens.

  • Last "However": because, as you said, `setState` is async (and even if it wasn't, the last code snippet doesn't set any state). The `search` method returns a promise (the results of `fetch`); [How do I return the response from an asynchronous call](https://stackoverflow.com/q/14220321/438992) discusses returning the results from an async function. – Dave Newton May 16 '23 at 15:32
  • Tangential: fetching and filtering are two separate operations; I'd recommend breaking down the problem into smaller parts. – Dave Newton May 16 '23 at 15:33

2 Answers2

0

I think something like this could work. Instead of doing this in your app.js :

temp.forEach(function (arrayTrack) {
      this.setState((prevState) => ({
        searchResults: [...prevState.searchResults, arrayTrack],
      }));
    });

Do the same thing but by mapping the provided result like the following :

this.setState((prevState)=>({
    searchResults: [...prevState.searchResults, ...temp]
})

By using this method you set only one time your state and prevent anything that could be wrong while trying to call and modify at the same time your state.

using the three dots in your case before temp will normally do the exact same thing like forEach.

PS: With the code you provided we cannot be sure that you are developping in React with hooks or class. Next time don't hesitate to precise it :)

To answer your last question, as you said useState is using asynchronous function to update the state when wanted. So, when you are updating your state and try to console.log just after, you will never find the good result because the state will not be updated yet.

In order to check your latest state value, you can use useEffect like this :

useEffect(()=>{
    console.log(yourstate);
},[yourstate])

In this way, you need to provide the name of your state as a dependency array. By providing it, it's like you said your function to watch every change on the state provided.

PS2: The rest of your code seem correct.

Hope I could help

Hades
  • 75
  • 6
  • thank you - using your suggestion, the temp array is populated correctly. But the searchResults are not being updated from what I can tell. I feel like I'm missing a piece of how setState updates the state and then how that gets sent to other components. – Laura Mansfield May 16 '23 at 16:55
  • Can you provide full code of your app.js ? – Hades May 17 '23 at 10:12
  • Hi yes - just added the rest of the code – Laura Mansfield May 17 '23 at 15:28
  • Ok, where did you look the updated value ? Is it this line juste after the render ? ```console.log("this.state.searchresults:", this.state.searchResults);``` If it is the case try the useEffect that i provide sooner and tell me if there is multiple change ? – Hades May 17 '23 at 15:34
0

I was able to fix my issues thanks to the responses. Dave's link to the other post was very helpful.

This is my current working code.

 search(term) {
    this.setState({ searchResults: [] });
    let temp = [];
    let filteredResults = [];
    for (let i = 0; i < 5; i++) {
      Spotify.search(term, (i * 50).toString())
        .then((response) => {
          filteredResults = Spotify.filterSearchResults(term, response);
          filteredResults.forEach(function (arrayTrack) {
            temp.push(arrayTrack);
          });
        })
        .then((response) => {
          this.setState((prevState) => ({
            searchResults: [...prevState.searchResults, ...temp],
          }));
        });
    }

    return temp;
  }