sarah
sarah

Reputation: 71

useContext give error Cannot read property '...' of undefined

I need help with this issue, my app component as in the image below. I want to store track object inselectedTrack in the state using useState when I click on the view details button. Then use it to display track details in instead of making another fetch from API to get tack details, but when I use useContext inside give me this error TypeError: Cannot read property 'selectedTrack' of undefined. React Components

import React from 'react';
import Header from './Header';
import Search from '../tracks/Search';
import Tracks from '../tracks/Tracks';
import Footer from './Footer';
import TrackContextProvider from '../../contexts/TrackContext';

const Main = () => {
  return (
    <div>
      <TrackContextProvider>
        <Header />
        <Search />
        <Tracks />
        <Footer />
      </TrackContextProvider>
    </div>
  );
};

export default Main;

TrackContext.js

import React, { createContext, useState, useEffect } from 'react';
export const TrackContext = createContext();

const TrackContextProvider = props => {

  const [tracks, setTracks] = useState([]);
  const [selectedTrack, setSelectedTrack] = useState([{}]);

  const API_KEY = process.env.REACT_APP_MUSICXMATCH_KEY;

  useEffect(() => {
    fetch(
      `https://cors-anywhere.herokuapp.com/https://api.musixmatch.com/ws/1.1/chart.tracks.get?chart_name=top&page=1&page_size=10&country=fr&f_has_lyrics=1&apikey=${API_KEY}`
    )
      .then(response => response.json())
      .then(data => setTracks(data.message.body.track_list))
      .catch(err => console.log(err));
    // to disable the warning rule of missing dependency
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // state for heading
  const [heading, setHeading] = useState(['Top 10 Tracks']);

  return (
    <TrackContext.Provider value={{ tracks, heading, selectedTrack, setSelectedTrack }}>
      {props.children}
    </TrackContext.Provider>
  );
};

export default TrackContextProvider;

import React, { Fragment, useContext } from 'react';
import { Link } from 'react-router-dom';
import { TrackContext } from '../../contexts/TrackContext';

const TrackDetails = () => {
  const { selectedTrack } = useContext(TrackContext);
  console.log(selectedTrack);
  return (
    <Fragment>
      <Link to="/">
        <button>Go Back</button>
      </Link>
      <div>
        {selectedTrack === undefined ? (
          <p>loading ...</p>
        ) : (
          <h3>
            {selectedTrack.track.track_name} by {selectedTrack.track.artist_name}
          </h3>
        )}
        <p>lyrics.............</p>
        <div>Album Id: </div>)
      </div>
    </Fragment>
  );
};

export default TrackDetails;

import React, { useState, useContext, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { TrackContext } from '../../contexts/TrackContext';

const Track = ({ trackInfo }) => {
  const { selectedTrack, setSelectedTrack } = useContext(TrackContext);

  const handleClick = e => {
    setSelectedTrack(trackInfo);
  };

  console.log(selectedTrack);

  return (
    <li>
      <div>{trackInfo.track.artist_name}</div>
      <div>Track: {trackInfo.track.track_name}</div>
      <div>Album:{trackInfo.track.album_name}</div>
      <div>Rating:{trackInfo.track.track_rating}</div>
      <Link to={{ pathname: `/trackdetails/${trackInfo.track.track_id}`, param1: selectedTrack }}>
        <button onClick={handleClick}>> View Lyric</button>
      </Link>
    </li>
  );
};

export default Track;

UPDATE: adding Tracks component

import React, { useContext, Fragment } from 'react';
import Track from './Track';
import { TrackContext } from '../../contexts/TrackContext';

const Tracks = () => {
  const { heading, tracks } = useContext(TrackContext);

  const tracksList = tracks.map(trackInfo => {
    return <Track trackInfo={trackInfo} key={trackInfo.track.track_id} />;
  });
  return (
    <Fragment>
      <p>{heading}</p>
      {tracks.length ? <ul>{tracksList}</ul> : <p>loading...</p>}
    </Fragment>
  );
};

export default Tracks;

Upvotes: 6

Views: 45359

Answers (1)

Scorpion-Prince
Scorpion-Prince

Reputation: 3634

I think the issue here is that since the selectedTrack is loaded asynchronously, when it is accessed from the context, it is undefined (you can get around the TrackContext being undefined by passing in a default value in the createContext call). Since the selectedTrack variable is populated anychronously, you should store it in a Ref with useRef hook, and return that ref as part of the context value. That way you would get the latest value of selectedTrack from any consumer of that context.


const selectedTracks = useRef([]);
useEffect(() => {
    fetch(
      `https://cors-anywhere.herokuapp.com/https://api.musixmatch.com/ws/1.1/chart.tracks.get?chart_name=top&page=1&page_size=10&country=fr&f_has_lyrics=1&apikey=${API_KEY}`
    )
      .then(response => response.json())
      .then(data => {
        selectedTrack.current = data.message.body.track_list;
      })
      .catch(err => console.log(err));
    // to disable the warning rule of missing dependency
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

Upvotes: 1

Related Questions