1
votes

In my app there's a component that renders (play)lists (I have 2 lists hardcoded ) I can Add a new list to the list of lists. When you click on a list the list of songs is displayed, and at the bottom of the list is a button that, when you click it, displays a form with inputs (title,artist,album). Before I fixed the adding list functionality, songs were added to the 'active' list but now the action is dispatched (ADD_SONG) and shows up with the right values in the (Redux)state but it renders the same type of element/component as the list and is not appened/added... I'm not sure where to look I hope someone can spot my faulty logic

AddSongForm

export default class AddSongForm extends React.PureComponent {
  constructor() {
      super();

      this.state = {
        clicked: false
      };

      this.handleClick = this.handleClick.bind(this)
    }

    handleClick() {
      this.setState({
        clicked: !this.state.clicked
      })
    }

  handleChange = (event) => {
    const value = event.target.value
    const name = event.target.name
// console.log(name, value)
// console.log(this.state);
    this.setState({
      [name]: value
    })
  }

  handleSubmit = (event) => {
    event.preventDefault()
console.log(this.state);
    if (this.state.title && this.state.artist) {
      this.props.addSong({
        title: this.state.title,
        artist: this.state.artist,
        album: this.state.album
      })
    }
  }

  render() {

    return (<div>
      <button onClick={this.handleClick}><h2>New Song+</h2></button>

  {this.state.clicked ?
     <form onSubmit={this.handleSubmit}>
        <label>
          Song Title:
          <input type="text" name="title" onChange={this.handleChange} />
        </label>
        <label>
        <br/>  Artist:
          <input type="text" name="artist" onChange={this.handleChange} />
        </label>
        <label>
        <br/>  Album:
          <input type="text" name="album" onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
      : null}
    </div>)
  }

}

AddSongFormContainer

import AddSongForm from './AddSongForm'
import { connect } from 'react-redux'

class AddSongFormContainer extends React.PureComponent {
  addSong = (song) => {
    this.props.dispatch({
      type: 'ADD_SONG',
      payload: {
        id: Math.ceil(Math.random()*10000),
        ...song
      }
    })
  }

  render() {

    return <AddSongForm addSong={this.addSong} />
  }
}

export default connect(null)(AddSongFormContainer)

Reducer with initial state

const initState = [

  {
    id: 1,
    title: 'Play list 1',
    data: [
      {
        id: 1,
        title: 'DogHeart II',
        artist: 'The Growlers',
        album: 'Gilded Pleasures'
      }, {
        id: 2,
        title: 'Beast of No nation',
        artist: 'Fela Kuti',
        album: 'Finding Fela'
      }, {
        id: 3,
        title: 'Satellite of love',
        artist: 'Lou Reed',
        album: 'Transformer'
      }
    ]
  }, {
    id: 2,
    title: 'Play list 2',
    data: [
      {
        id: 1,
        title: 'Whatever happend to my Rock and Roll',
        artist: 'BlackRebelMoterCycleClub',
        album: 'B.R.M.C'
      }, {
        id: 2,
        title: 'U Sexy Thing',
        artist: 'Crocodiles',
        album: 'CryBaby Demon/ U Sexy Thing'
      }, {
        id: 3,
        title: 'Oh Cody',
        artist: 'NoBunny',
        album: 'Raw Romance'
      }
    ]
  }
]
const reducer = (state = initState, action = {}) => {
  switch (action.type) {

    case 'ADD_LIST':
      return [
        ...state,
        action.payload
      ]
    case 'ADD_SONG':
      return [
        ...state,
        action.payload
      ]

    default:
      return state
  }
}

export default reducer

PlayList Component mapping over al the songs in the list

export default class PlayList extends React.Component{
  constructor() {
      super()

      this.state = {
        clicked: false
      }
      this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
      this.setState({
        clicked: !this.state.clicked
      })
      console.log(this.props.selectList.name)

    }


render(){
  // console.log(this.props);
// console.log(this.props.playLists[0].data);
  return (<div>
    <button  onClick={this.handleClick}><h1>{this.props.playLists.title}</h1></button>
{this.state.clicked ?
  <ul>
      { this.props.playLists.data.map(song =>
        <li key={song.id} onClick={() => this.props.selectSong(song.id)}>
          <b>{song.title}</b><br/>
            By:
            <br/>
            <h3><b>{song.artist}</b></h3><br/>
             Appears on:
              <b>{song.album}</b><br/><br/>
        </li>
      ) }
      <AddSongFormContainer/>
    </ul>


     : null}


  </div>)
  }
}

Container for al the playlists in the initialstate(array)

class PlayListsContainer extends React.PureComponent {


  selectSong = (id) => {
  this.props.dispatch({
    type: 'SELECT_SONG',
    payload: id
  })
}

  selectSong(id) {
    console.log('selected song:', id)
  }
  selectList = (id) => {
    this.props.dispatch({
      type: 'SELECT_LIST',
      payload: id
    })
  }

    selectList(id) {
      console.log('selected song:', id)
    }

    render() {
        const playlistsArray = this.props.playLists
        // console.log(playlistsArray)
        return (
            playlistsArray.map((playlist) => <PlayList
                playLists={playlist}
                selectSong={this.selectSong}
                selectList={this.selectList}
                key={playlist.id}
            />)

        )
    }








}

const mapStateToProps = (state) => {
// console.log(state.playLists);
  return {
    playLists: state.playLists



  }
}

export default connect(mapStateToProps)(PlayListsContainer)

redux state after submitting a new song

1
"shows up with the right values in the (Redux)state" - I assume you are seeing this via redux dev tools? If everything is working correctly in Redux, your problem might be in the presentation of the list of songs. Can you post your code for the react component/container responsible for showing the songs?nxSolari
added the playlist component where I map over the songs in the list and the playlist container where I map over the lists.newDev
I should add , that everything is not working correctly in redux It dispatches the action and ADD_SONG shows up in my inspector . and in the state I have playLists ( an array which contains initially 2 play lists) and it is added to that array (as a playlist I guess) but a play list has as keys id,title and data(array) the values(title,artist,album) should go into the data arraynewDev
I added a large update to my answer that has the logic for the new action & reducer. Hope it helps!nxSolari

1 Answers

1
votes

With your comment describing your redux problems, and the screenshot of the Redux dev tools - the problem is clear now.

When you are adding a song, you are simply adding it to the top level of the store, without actually adding it to a play list.

It would be entirely possible to fix this as is. In your reducer, rather than adding the song like you do now, you need to add it specifically to a playlist. If you need a code example, I can provide one.

However, I encourage you to refactor your redux store - and follow the best practicing of having a normalized, flat state.

What this means is, you want to have two top-level objects for your redux store.

playlists
songs

Rather than including all of the data about a song in a playlist, you simply reference the id of the songs.

Your playlists would look like this:

playlists { 
  1: {
  title: 'my playlist'
  songs: [1,2,3]}

And the songs can stay the same.

Whenever you add a song to a playlist, you simply add the song, and then update the playlist with the new song id.

Another practice you can do, to make your code a bit cleaner is to use mapDispatchToProps rather than defining your redux action dispatches inline. Docs for that are here.

#

To fix the code as is, the main thing we need to do is pass along the playlistid that you want to add the song to. Otherwise, how else will we know where to put the song?

First, update your action in your addSongFormContainer to accept an additional argument, targetPlaylist (that the song will go into)

  addSong = (song, targetPlaylist) => {
this.props.dispatch({
  type: 'ADD_SONG',
  payload: {
    playlist: targetPlaylist
    id: Math.ceil(Math.random()*10000),
    ...song
    }
  })
}

The usage of this action now requires you pass along a target playlist. For brevity, I am going to hardcode that the song is being added to playlist 1. I'll leave the exercise of passing the selected playlist down to the component up to you.

I cleaned up the handleSubmit to make it more clear, by moving the song into it's own variable as well.

  handleSubmit = (event) => {
  event.preventDefault()
  console.log(this.state);

  if (this.state.title && this.state.artist) {

    let song = {
      title: this.state.title,
      artist: this.state.artist,
      album: this.state.album
    }
    let selectedPlayList = 1 //Fix this later :)
    this.props.addSong(song, selectedPlayList)
  }
}

Now the last problem is the reducer.

case 'ADD_SONG':
      const index = store.getState().findIndex(playlist => playlist.id === 
        action.payload.playlist)
      console.log(index) //This should be index 0, after looking up playlist id: 1
      const updatedPlaylistSongs = this.state[index].data
      updatedPlaylistSongs.push(action.playload.song)
      return [
        ...state.slice(0, index), // All playlists before current
        {
          ...state[index], //The targeted playlist.
          ...updatedPlaylistSongs //The updated songs
        },
        ...state.slice(index + 1), //All playlists after current
    ]

I hope the reducer works for you, though it might need a bit of work - I am not used to writing reducers dealing with arrays. I typically have normalized data, which results in much easier modification. I highly encourage you to attempt to normalize your redux store. Stop using arrays, try using objects where the key is generated (use uuidv4 to make a unique & random key). This makes "selecting" what you want to edit/update significantly easier.

I hope this helps!