Jammming Project, reloads on every other search

I’m working on the Jammming project and I have an issue where upon searching the first time, the app returns results correctly. However, on another press of the search button, the entire app seems to reload and I lose any state that was already set. The playlist and search results both reset to the mock data I used to fill the original page.

App.js

import React, { useCallback, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import "./App.css";
import Playlist from "./components/Playlist";
import SearchBar from "./components/SearchBar";
import SearchResults from "./components/SearchResults";
import Spotify from "./helpers/Spotify";

function App() {
  const mockResults = [
    {
      id: 1,
      playlistId: uuidv4(),
      title: "Bat Country",
      artist: "Avenged Sevenfold",
      album: "Bat Country",
    },
    {
      id: 2,
      playlistId: uuidv4(),
      title: "Enter Sandman",
      artist: "Metallica",
      album: "Metallica (Remastered)",
    },
  ];

  const mockPlaylist = [
    {
      id: 1,
      playlistId: uuidv4(),
      title: "Bat Country",
      artist: "Avenged Sevenfold",
      album: "Bat Country",
    },
  ];

  const [searchResults, setSearchResults] = useState(mockResults);
  const [playlist, setPlaylist] = useState(mockPlaylist);
  const [playlistName, setPlaylistName] = useState("New Playlist");

  const handleAddSong = useCallback((newSong) => {
    const trackWithId = { ...newSong, playlistId: uuidv4() };
    setPlaylist((prevList) => [...prevList, trackWithId]);
  }, []);

  const handleRemoveSong = useCallback((songToRemove) => {
    setPlaylist((prevList) => {
      return prevList.filter(
        (song) => song.playlistId !== songToRemove.playlistId
      );
    });
  }, []);

  const handleSearch = useCallback(async (term) => {
    const resultTracks = await Spotify.search(term);
    const resultTracksWithId = resultTracks.map((track) => {return {...track, playlistId: uuidv4()}});
    setSearchResults( resultTracksWithId);
  }, []);

  const handlePlaylistNameChange = useCallback(
    (newName) => setPlaylistName(newName),
    []
  );

  return (
    <div className="App">
      <header className="App-header">
        <h1>Jammming</h1>
        <p>Build your perfect Spotify playlists</p>
      </header>
      <div className="searchBar">
        <SearchBar onSearch={handleSearch} />
      </div>
      <div className="App-body">
        <SearchResults
          tracks={searchResults}
          action="+"
          actionMethod={handleAddSong}
        />
        <Playlist
          tracks={playlist}
          playlistName={playlistName}
          changeName={handlePlaylistNameChange}
          action="-"
          actionMethod={handleRemoveSong}
        />
      </div>
      <footer>
        {`Made with <3 by Kyle Buck | `}
        <a href="https://github.com/BuckBuckGoose/jammming">Github</a>
      </footer>
    </div>
  );
}

export default App;

Spotify.js

const clientId = "[removed from post]";
const redirectUri = "http://localhost:3000";
const baseUrl = "https://api.spotify.com/v1";
let accessToken;

const Spotify = {
  getAccessToken() {
    console.log(`Get: ${accessToken}`);
    if (accessToken) {
      console.log(`Storage: ${accessToken}`);
      return accessToken;
    }

    const accessTokenMatch = window.location.href.match(/access_token=([^&]*)/);
    const expiresInMatch = window.location.href.match(/expires_in([^&]*)/);

    if (accessTokenMatch && expiresInMatch) {
      accessToken = accessTokenMatch[1];
      const expiresIn = Number(expiresInMatch[1]);
      window.setTimeout(() => (accessToken = ""), expiresIn * 1000);
      window.history.pushState("Access Token", null, "/");
      console.log(`Retrieve: ${accessToken}`);
      return accessToken;
    } else {
      const accessUrl = `https://accounts.spotify.com/authorize?client_id=${clientId}&response_type=token&scope=playlist-modify-public&redirect_uri=${redirectUri}`;
      window.location = accessUrl;
    }
  },

  async search(term) {
    const accessToken = Spotify.getAccessToken();
    return await fetch(`${baseUrl}/search?type=track&q=${term}`, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    })
      .then((response) => {
        return response.json();
      })
      .then((jsonResponse) => {
        if (!jsonResponse.tracks) {
          return [];
        }
        return jsonResponse.tracks.items.map((track) => ({
          id: track.id,
          name: track.name,
          artist: track.artists[0].name,
          album: track.album.name,
          uri: track.uri,
        }));
      });
  },

  async savePlaylist(name, trackUris) {
    if (!name || !trackUris.length) {
      return;
    }

    const accessToken = Spotify.getAccessToken();
    const headers = { Authorization: `Bearer ${accessToken}` };
    let userId;

    return await fetch(`${baseUrl}/me`, { headers: headers })
      .then((response) => response.json())
      .then((jsonResponse) => {
        userId = jsonResponse.id;
        return fetch(`${baseUrl}/users/${userId}/playlists`, {
          headers: headers,
          method: "POST",
          body: JSON.stringify({ name: name }),
        })
          .then((response) => response.json())
          .then((jsonResponse) => {
            const playlistId = jsonResponse.id;
            return fetch(
              `${baseUrl}/users/${userId}/playlists/${playlistId}/tracks`,
              {
                headers: headers,
                method: "POST",
                body: JSON.stringify({ uris: trackUris }),
              }
            );
          });
      });
  },
};

export default Spotify;

SearchBar.js

import React, {useState} from 'react';

function SearchBar(props) {

    const [searchText, setSearchText] = useState('');

    const handleTextUpdate = (e) => setSearchText(e.target.value);
    const handleButtonPress = (e) => {
        e.preventDefault();
        props.onSearch(searchText)};

    return (
        <form>
            <input type='text' name='searchBar' id='searchBar' value={searchText} onChange={handleTextUpdate} />
            <button type='button' name='searchButton' id='searchButton' onClick={handleButtonPress}>Search</button>
        </form>
    );
}

export default SearchBar;

In App.js, in your handleAddSong function, you have an empty dependency array. Could you try the below and let me know if it works? If it does, you’ll have to ensure you add dependencies for the relevant useCallback invocations.

I’m not entirely sure of the why but I suspect it prevents re-rendering everything, which resets the reference to the state, or something like that. I got stuck there too and had to refer to the example code (which I really did not want to do) when I couldn’t get it working.

const handleAddSong = useCallback((newSong) => {
    const trackWithId = { ...newSong, playlistId: uuidv4() };
    setPlaylist((prevList) => [...prevList, trackWithId]);
  }, [playlist]);