Jammming - Undefined onRemove prop

Hi everyone,

I’m struggling to figure out why my onRemove prop is undefined in the Track.jsx file for the Jammming project (https://www.codecademy.com/journeys/full-stack-engineer/paths/fscj-22-front-end-development/tracks/fscj-22-react-part-ii/modules/wdcp-22-jammming-54dd6aa4-39da-42be-989f-67a12f65b1a8/kanban_projects/jammming-react18)

I console logged the props at each passing and everything is fine, meaning all props are defined, until the last child in the sequence, App=>PlayList=>TrackList=>Track, which is undefined for onRemove. All the other props are defined and addTrack works as expected using mock data, but remove track doesn’t work.

The console error is "Uncaught TypeError: props.onRemove is not a function, which references the second line of this code contained in the Track.jsx file:

const removeTrack = useCallback(() => {
        props.onRemove(props.track)},
        [props.onRemove, props.track]);

I included the full relevant code below.

Any help is much appreciated, especially since I have been stuck for hours trying to resolve it, including reviewing the community board.

I used the Vite development environment for this project instead of create-react-app.

-John

App,jsx

import React, {useState, useCallback} from 'react';
import styles from './App.module.css';
import SearchBar from '../SearchBar/SearchBar.jsx'
import SearchResults from '../SearchResults/SearchResults.jsx'
import PlayList from '../PlayList/PlayList.jsx';

function App() {
    const [searchResults, setSearchResults] = useState([
    {trackName: 'Sounds of Madness',
    artist: 'Shinedown',   
    album: 'The Sound of Madness',
    id: 0
    },
    {trackName: 'Already Over',
    artist: 'Mike Shinoda',   
    album: 'Evolution of Mike Shinoda',
    id: 1
    },
    {trackName: 'Dilemma',
    artist: 'Green Day',   
    album: 'Saviors',
    id: 2
    }
    ]);

    const [playListTracks, setPlayListTracks] = useState([
        {trackName: 'Do You Really Want It',
        artist: 'Nothing More',   
        album: 'The Stories We Tell Ourselves',
        id: 10
        }]);

    const addTrack = useCallback((track) => {
        if(playListTracks.some((savedTrack) => savedTrack.id === track.id)) {
            alert('Track previously added to playlist.');
        }   else {  
            setPlayListTracks((prevTracks) => {
                return [track, ...prevTracks];
            })}}, [playListTracks]);

    const removeTrack = useCallback((track) => { 
        setPlayListTracks((prevTracks) => 
        prevTracks.filter((currentTrack) => currentTrack.id !== track.id));
        }, []);   
    
    return ( 
        <div>
            <SearchBar />
            <div className={styles.flexContainer}>
                <SearchResults 
                    tracks={searchResults} 
                    onAdd={addTrack} 
                />
                <PlayList
                    tracks={playListTracks} 
                    onRemove={removeTrack}
                />
            </div>
        </div>
    )
} 

export default App;

PlayList.jsx

import React from 'react';
import styles from './PlayList.module.css';
import TrackList from '../TrackList/TrackList.jsx'

function PlayList(props) {
    return (
        <div className={styles.playList}>
            <form>
                <input className={styles.playListName}
                    type='text'
                    name='playListName'
                    id='playListName'
                    placeholder='Input play list name'
                >
                </input>
            </form>
        
            <TrackList 
                tracks={props.tracks} 
                OnRemove={props.onRemove}
                /> 

        <div className={styles.btnContainer}>
                <button className={styles.btn}>Save to Spotify</button>
            </div>
        </div>
    )
}

export default PlayList;

SearchResults.jsx

import React from 'react';
import styles from './SearchResults.module.css';
import TrackList from '../TrackList/TrackList.jsx'


function SearchResults(props) {
    return (
        <div className={styles.searchResults} >
            <h1 className={styles.header}>Search Results</h1>
            <TrackList 
                tracks={props.tracks} 
                onAdd={props.onAdd} 
                isSearchResults={true}
            /> 
            </div>
    )

}

export default SearchResults;

TrackList.jsx

import React from 'react';
import Track from '../Track/Track.jsx';

function TrackList (props){
    return (
        props.tracks.map((track)=> (
            <Track 
            track={track} 
            key={track.id} 
            onAdd={props.onAdd} 
            onRemove={props.onRemove}
            isSearchResults={props.isSearchResults}
            />
        ))
    )
}
    
export default TrackList;

Track.jsx

import React, {useCallback} from 'react';
import styles from './Track.module.css';

function Track (props) {

console.log(props);//Yields undefined for onRemove

// Functions to add tracks to playlists from search results and remove tracks from playlists 
    const addTrack = useCallback(() => {
        props.onAdd(props.track)},
        [props.onAdd, props.track]);

    const removeTrack = useCallback(() => {
        props.onRemove(props.track)},
        [props.onRemove, props.track]);

    const renderIcon = () => {
        if(props.isSearchResults){
            return (
                <span
                    id={styles.addIcon}
                    className='material-symbols-outlined' //Required by Google to render icon
                    onClick={addTrack}
                    >playlist_add
                </span>
            );
        };
            return (
                <span
                    id={styles.removeIcon}
                    className='material-symbols-outlined'
                    onClick={removeTrack}
                    >playlist_remove
                </span>
            );
        };
        
       return (
        <div className={styles.track}>
            <div className={styles.alignItems}>
                <span className="material-symbols-outlined">music_note</span>
                <span className={styles.trackInfo}>{props.track.trackName}</span>
                {renderIcon()}
            </div>
            <div className={styles.alignItems}>
                <span className="material-symbols-outlined">artist</span>
                <span className={styles.trackInfo}>{props.track.artist}</span>
            </div>
            <div className={styles.alignItems}>
                <span className="material-symbols-outlined">album</span>
                <span className={styles.trackInfo}>{props.track.album}</span>
            </div>
        </div>
    );
}

export default Track;

Because you don’t pass the onRemove prop to the Tracklist in SearchResults.jsx:

You do not need onRemove in the SearchResults list, so that’s fine. But then you should only call props.onRemove in Track.jsx if it is defined, which is a little tricky when using useCallback. Btw, why do you use useCallback in Track.jsx?

Thanks for the reply mirja.

I didn’t pass the onRemove prop for TrackList because it’s not required for the search results, only the playlist. It makes sense that onRemove is undefined for search results created using Track.jsx. It should be defined for tracks created using PlayList.jsx.

What doesn’t make sense to me is why onRemove is undefined for PlayList tracks where onRemove is properly passed in, as far as I can tell, to every file in the playList file sequence using props.

I added onAdd and isSearchResults to App, SearchResults and PLayList.jsx and the other relevant files to see if it makes any difference. Unfortunately, Track.jsx still logs undefined for onRemove. The other new props are defined properly.

Unsurprisingly, I continue to receive an “Uncaught TypeError: props.onRemove is not a function” since onRemove continues to be undefined. Any ideas on how to correct this?

My original code didn’t include useCallback for addTrack and removeTrack, but I added it after evaluating the Codecademy solution to improve app performance. Should I remove it?

Below is the updated code:

App.jsx

import React, {useState, useCallback} from 'react';
import styles from './App.module.css';
import SearchBar from '../SearchBar/SearchBar.jsx'
import SearchResults from '../SearchResults/SearchResults.jsx'
import PlayList from '../PlayList/PlayList.jsx';

function App() {
    const [searchResults, setSearchResults] = useState([
    {trackName: 'Sounds of Madness',
    artist: 'Shinedown',   
    album: 'The Sound of Madness',
    id: 0
    },
    {trackName: 'Already Over',
    artist: 'Mike Shinoda',   
    album: 'Evolution of Mike Shinoda',
    id: 1
    },
    {trackName: 'Dilemma',
    artist: 'Green Day',   
    album: 'Saviors',
    id: 2
    }
    ]);

    const [playListTracks, setPlayListTracks] = useState([
        {trackName: 'Do You Really Want It',
        artist: 'Nothing More',   
        album: 'The Stories We Tell Ourselves',
        id: 10
        }]);

    const [playListName, setPlayListName] = useState('');

    const updatePlayListName = useCallback((event) => {
        setPlayListName(event.target.value)},[]); 
    
    const addTrack = useCallback((track) => {
        if(playListTracks.some((savedTrack) => savedTrack.id === track.id)) {
            alert('Track previously added to playlist.');
        }   else {  
            setPlayListTracks((prevTracks) => {
                return [track, ...prevTracks];
            })}}, [playListTracks]);

    const removeTrack = useCallback((track) => { 
        setPlayListTracks((prevTracks) => 
        prevTracks.filter((currentTrack) => currentTrack.id !== track.id));
        }, []);  
 
    return ( 
        <div>
            <SearchBar />
            <div className={styles.flexContainer}>
                <SearchResults 
                    tracks={searchResults} 
                    onAdd={addTrack}
                    onRemove={removeTrack} 
                    isSearchResults={true}
                />
                <PlayList
                    tracks={playListTracks} 
                    onAdd={addTrack}
                    onRemove={removeTrack}
                    updateListName={updatePlayListName}
                    listName={playListName}
                    isSearchResults={false}
                />
            </div>
        </div>
    )
} 

export default App;

SearchResults.jsx

import React from 'react';
import styles from './SearchResults.module.css';
import TrackList from '../TrackList/TrackList.jsx'

function SearchResults(props) {
    return (
        <div className={styles.searchResults} >
            <h1 className={styles.header}>Search Results</h1>
            <TrackList 
                tracks={props.tracks} 
                onAdd={props.onAdd} 
                isSearchResults={props.isSearchResults}
                onRemove={props.onRemove}
            /> 
            </div>
    )
}

export default SearchResults;

PLayList.jsx

import React from 'react';
import styles from './PlayList.module.css';
import TrackList from '../TrackList/TrackList.jsx'

function PlayList(props) {
    return (
        <div className={styles.playList}>
            <form>
                <input className={styles.playListName}
                    type='text'
                    name='playListName'
                    id='playListName'
                    placeholder='New Playlist' 
                    value={props.listName}
                    onChange={props.updateListName}
                >
                </input>
            </form>
        
            <TrackList 
                tracks={props.tracks} 
                OnRemove={props.onRemove}
                onAdd={props.onAdd}
                isSearchResults={props.isSearchResults}
                /> 

        <div className={styles.btnContainer}>
                <button className={styles.btn}>Save to Spotify</button>
            </div>
        </div>
    )
}

export default PlayList;

TrackList.jsx

import React from 'react';
import Track from '../Track/Track.jsx';

function TrackList (props){
    return (
        props.tracks.map((track)=> (
            <Track 
            track={track} 
            key={track.id} 
            onAdd={props.onAdd} 
            onRemove={props.onRemove}
            isSearchResults={props.isSearchResults}
            />
        ))
    )
}
    
export default TrackList;

Track.jsx

import React, {useCallback} from 'react';
import styles from './Track.module.css';

function Track (props) {

console.log(props);//Yields undefined for onRemove

// Functions to add tracks to playlists from search results and remove tracks from playlists 
    const addTrack = useCallback(() => {
        props.onAdd(props.track)},
        [props.onAdd, props.track]);

    const removeTrack = useCallback(() => {
        props.onRemove(props.track)},
        [props.onRemove, props.track]);

    const renderIcon = () => {
        if(props.isSearchResults){
            return (
                <span
                    id={styles.addIcon}
                    className='material-symbols-outlined' //Required by Google to render icon
                    onClick={addTrack}
                    >playlist_add
                </span>
            );
        };
            return (
                <span
                    id={styles.removeIcon}
                    className='material-symbols-outlined'
                    onClick={removeTrack}
                    >playlist_remove
                </span>
            );
        };
        
       return (
        <div className={styles.track}>
            <div className={styles.alignItems}>
                <span className="material-symbols-outlined">music_note</span>
                <span className={styles.trackInfo}>{props.track.trackName}</span>
                {renderIcon()}
            </div>
            <div className={styles.alignItems}>
                <span className="material-symbols-outlined">artist</span>
                <span className={styles.trackInfo}>{props.track.artist}</span>
            </div>
            <div className={styles.alignItems}>
                <span className="material-symbols-outlined">album</span>
                <span className={styles.trackInfo}>{props.track.album}</span>
            </div>
        </div>
    );
}

export default Track;

In Playlist.jsx, you’ve got OnRemove in a capital letter. Change to onRemove, see if that works.

3 Likes

Thank you very much. It works perfectly now.

1 Like