-1

I have a React app where I am trying to display data fetched from my backend. My goal is to fetch my data (spotify playlists, tracks, and artists) onclick of one of the genres. then group them all together with promise.all, and send them to the results component with usenavigate:

navigate(`/${genre}`, { state: { artists, playlists, tracks } });

Then in Results, send the relevant data to it's component (playlists data to playlists component, tracks data to tracks component, and artists data to artists componenet). but when I click one of the artists, I still want to send the post request and be routed to /${artist.name} and send all the data that comes back with it to be displayed in the toptracks component. I'm pretty positive the data IS being sent to the toptracks component. there is just something wrong with the data that is getting passed to the artists, playlists, and tracks components. it is somehow becoming undefined, and I'm not sure why.

These are my errors:

Cannot destructure property 'artists' of 'props.data.artists' as it is undefined.

Uncaught TypeError: Cannot read properties of undefined (reading 'playlists')

Uncaught TypeError: Cannot read properties of undefined (reading 'tracks')

here are the components:

Genres.js

import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";

function Genres() {
  const [genres, setGenres] = useState([]);
  const navigate = useNavigate();

  useEffect(() => {
    fetch("/api/genres/")
      .then((response) => response.json())
      .then((data) => setGenres(data.genres))
      .catch((error) => console.log(error));
  }, []);

  function handleClick(genre) {
    const query_params = {
      genre: genre
    };
  
    Promise.all([
      fetch("/api/artists/", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(query_params),
      }),
      fetch("/api/playlists/", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(query_params),
      }),
      fetch("/api/tracks/", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(query_params),
      })
    ])
      .then((responses) => Promise.all(responses.map((response) => response.json())))
      .then(([artists, playlists, tracks]) => {
        console.log({ artists, playlists, tracks });
        navigate(`/${genre}`, { state: { artists, playlists, tracks } });
      })
      .catch((error) => console.log(error));
  }

  return (
    <div>
      <div className="genre-list-container">
        <ul className="genre-list">
          {genres.map((genre) => (
            <li
              className="genre"
              key={genre}
              onClick={() => handleClick(genre)}
            >
              {genre}
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

export default Genres;

Results.js

import React from 'react'
import Artists from './Artists'
import Playlists from './Playlists'
import Tracks from './Tracks'
import { useLocation } from "react-router-dom";


function Results() {
  const location = useLocation();

  return (
    <div>
      <Artists data={location.state} />
      <Playlists data={location.state} />
      <Tracks data={location.state} />
    </div>
  );
}

export default Results;

Artists.js

import React, { useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";

function Artists(props) {
  const { artists } = props.data.artists;

  const navigate = useNavigate();

  function handleClick(artist) {
    fetch("/api/top_tracks/", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ artist_id: artist.id }),
    })
      .then((response) => response.json())
      .then((data) => {
        console.log(data);
        navigate(`/${artist.name}`, { state: { data } });
      });
  }
  // console.log(artists.map((artist) => (artist.name)))
  return (
    <div>
      <div>Artists:</div>
      {artists.map((artist) => (
        <div key={artist.id}>
          <img src={artist.image_url} alt={artist.name} />
          <h1 onClick={() => handleClick(artist)}>{artist.name}</h1>
          <p>Popularity: {artist.popularity}</p>
          <p>Genres: {artist.genres}</p>
        </div>
      ))}
    </div>
  );
}
export default Artists;

TopTracks.js

import React from "react";
import { useLocation } from "react-router-dom";

function TopTracks() {
  const location = useLocation();
  const tracks = location.state;

  return (
    <div>
      
    </div>
  );
}

export default TopTracks;

Playlists.js and Tracks.js are both just console logging the data like so:

import React from "react";

function Tracks(props) {
  const tracks = props.data.tracks.tracks;
  console.log(tracks);
  return (
    <div>

    </div>
  );
}

export default Tracks;

1 Answers1

1

You are destructuring those properties in the wrong way, in Artist.js the line of code:

const { artists } = props.data.artists;

^ Wrong.

Should instead be:

const { artists } = props.data;

Or:

const artists = props.data.artists;

Same goes for the Tracks.js:

Wrong:

const tracks = props.data.tracks.tracks;

Correct 1:

const tracks = props.data.tracks;

Correct 2:

const { tracks } = props.data;

As for the Playlists.js you haven't provided its code, but I'm guessing you are doing a similar destructuring mistake, if there's different error then provide its code and I'll update the answer.

Aleksandar
  • 844
  • 1
  • 8
  • 18
  • If I assign `const {artists} = props.data` and console log it I get this object: `{total: 32, artists: Array(32)}`. So I need to do `const {artists} = props.data.artists`. Your comment however made me realize that my tracks, and playlists variables were not destructuring the same way as the artists. I fixed them to all look the same `const {__} = props.data.__`. They all have the same error now. `Cannot destructure property '__' of 'props.data.__' as it is undefined.` – Scottsdaaale Apr 15 '23 at 00:08
  • 1
    @Scottsdaaale for the second part what does `console.log`ging them Playlists and Tracks logs? For the first part, if the log is successful: it does show an `artists` property you are expecting then it seems like it is not related to the code above and you have updated something and you can successfully use your way for the *Artists.js* – Aleksandar Apr 15 '23 at 00:39
  • I'm sorry if my question wasn't clear. I am not having an issue rendering the artists, playlists, and tracks in the results component. It is when I click on one of the artists to get toptracks that I get the errors. Data for playlists and tracks log as expected before clicking on an artist. – Scottsdaaale Apr 15 '23 at 00:43
  • 1
    Well the only thing that catches my eyes is that `state` you are passing to `navigate()` seems like a big data, but I can't find any info about it on their official [documentations](https://reactrouter.com/en/main/hooks/use-navigate) as if some limitations or perhaps something interrupts it, I can't tell. But even if there's no limits, it should be better if the link has "*id*" query and that's how you'd fetch the data. Because your current way of passing data is quite something I've never encountered. – Aleksandar Apr 15 '23 at 00:53
  • The only way I found to send props to a dynamic route was to use useNavigate [like this](https://stackoverflow.com/questions/71380596/pass-data-to-a-component-with-usenavigate-from-react-router-dom?noredirect=1&lq=1) – Scottsdaaale Apr 15 '23 at 01:17
  • 1
    Another thing you can try before mapping over `artists` a condition `artists !== undefined && ...`, but then your error seems to be coming from the destructuring line, maybe try Logical OR operator to pass it an default state, like:`const { artists = [] } = props?.data?.artists || [];` so that you at least have `[]`, but again this would be some kung-fu quick fix, overall you have issues with side-effects if on initial render you are getting an expected data, but on re-render you aren't. – Aleksandar Apr 15 '23 at 01:20
  • 1
    As for passing data over components easily, have you heard about Redux, specifically [Redux Toolkit](https://redux-toolkit.js.org) ? It's based on Redux just made so much easier. – Aleksandar Apr 15 '23 at 01:23
  • I am a relatively new developer and have never used redux. Would you recommend I incorporate this into my project instead of what I am trying to do right now? – Scottsdaaale Apr 15 '23 at 01:26
  • 1
    Yes you certainly can, but you'd need to refactor quite a bit of your code – Aleksandar Apr 15 '23 at 01:34
  • 1
    And about the issue that you said appears `onClick`, what does the `console.log(data);` logs when you call `handleClick(artist)` inside *Artists.js* ? – Aleksandar Apr 15 '23 at 01:35
  • it logs the toptrack data object. `{tracks: Array(10)}` – Scottsdaaale Apr 15 '23 at 01:40
  • I think it is worth noting that another strange thing happens when i click this onclick. i get a console log from the results component: `{data: {…}} data : {tracks: Array(10)} [[Prototype]] : Object` this console log is logging useLocation state. – Scottsdaaale Apr 15 '23 at 01:42
  • 1
    Yeah like I said it seems to be resetting/interrupting the value. I can't quite tell, but it feels like buggy thing to work with since those functions, as name suggests, should be used for navigation, and the `state` property is there for some smaller data rather than what you're aiming to do. – Aleksandar Apr 15 '23 at 01:49
  • 1
    I see. I think I will try to implement state management. There isn't too much code on my frontend for me to refactor anyways. Really appreciate your help! – Scottsdaaale Apr 15 '23 at 02:06