Reputation: 3954
I have a playlist component that contains a list of songs. OnComponentDidMount it gets data from axios and populates it to a list in the store with the action GET_PLAYLIST.
There is an Add to Playlist button that calls an UPDATE_PLAYLIST action that should add a selected song to the playlist list in store, then an axios post call adds the new song to the firebase database.
The playlist component should update when the state changes and I can't figure out why it isn't?
My Playlist.js component:
import React, { Component } from "react";
import { connect } from "react-redux";
import Auxilliary from "../../hoc/Auxilliary";
import PlayListItem from "../../components/PlayListItem/PlayListItem";
import classes from "./Playlist.css";
import * as actionTypes from "../../store/actions";
import axios from "../../hoc/axios-Firebase";
class Playlist extends Component {
componentDidMount() {
const searchQuery = "/playlist.json";
axios
.get(searchQuery)
.then(response => {
const playlistSongs = [];
for (let song in response.data) {
playlistSongs.push({
artistName: response.data[song].artistName,
trackName: response.data[song].track,
collectionName: response.data[song].collectionName
});
}
this.props.onGetPlaylist(playlistSongs);
})
.catch(error => {
console.log(error);
});
}
render() {
if (this.props.playlist.length > 0) {
const results = this.props.playlist.map((playlistItem, index) => {
return (
<PlayListItem
key={index}
artist={playlistItem.artistName}
track={playlistItem.trackName}
album={playlistItem.collectionName}
/>
);
});
return (
<Auxilliary>
<table className={classes.playlistTable}>
<thead>
<tr>
<th>Track</th>
<th>Album</th>
<th>Artist</th>
</tr>
</thead>
<tbody>{results}</tbody>
</table>
</Auxilliary>
);
} else {
return null;
}
}
}
const mapStateToProps = state => {
return {
playlist: state.playlist
};
};
const mapDispatchToProps = dispatch => {
return {
onGetPlaylist: value =>
dispatch({
type: actionTypes.GET_PLAYLIST,
playlist: value
})
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Playlist);
The component with the Add to Playlist button:
import React, { Component } from "react";
import { connect } from "react-redux";
import Auxilliary from "../../hoc/Auxilliary";
import * as actionTypes from "../../store/actions";
import axios from "../../hoc/axios-Firebase";
import classes from "./SongInfo.css";
class SongInfo extends Component {
constructor(props) {
super(props);
this.addToPlaylistHandler = this.addToPlaylistHandler.bind(this);
}
addToPlaylistHandler(
track,
trackID,
artistName,
collectionName,
kind,
trackPrice
) {
const data = {
track: track,
trackID: trackID,
artistName: artistName,
collectionName: collectionName,
kind: kind,
trackPrice: trackPrice
};
this.props.onUpdatePlaylist(data);
axios
.post("/playlist.json", data)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
}
render() {
return (
<Auxilliary>
<table>
<thead>
<tr>
<th>Track</th>
<th>Track ID</th>
<th>Artist Name</th>
<th>Collection Name</th>
<th>Kind</th>
<th>Track Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>{this.props.track}</td>
<td>{this.props.id}</td>
<td>{this.props.artistName}</td>
<td>{this.props.collectionName}</td>
<td>{this.props.kind}</td>
<td>{this.props.trackPrice}</td>
</tr>
</tbody>
</table>
<button
className={classes.addToPlaylist}
onClick={this.addToPlaylistHandler(
this.props.track,
this.props.id,
this.props.artistName,
this.props.collectionName,
this.props.kind,
this.props.trackPrice
)}
>
Add to Playlist
</button>
</Auxilliary>
);
}
}
const mapStateToProps = state => {
return {
playlist: state.playlist
};
};
const mapDispatchToProps = dispatch => {
return {
onUpdatePlaylist: value =>
dispatch({
type: actionTypes.UPDATE_PLAYLIST,
playlistItem: value
})
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(SongInfo);
My reducer:
import * as actionTypes from "./actions";
const initialState = {
searchValue: "",
finalSearchValue: "",
searchResults: [],
sliceStart: 0,
sliceEnd: 25,
nextDisabled: false,
prevDisabled: true,
searchResult: {
id: null,
artistId: null,
artistName: "",
track: "",
collectionName: "",
kind: "",
trackPrice: null
},
playlist: []
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.UPDATE_INPUT_VALUE:
return {
...state,
searchValue: action.searchValue
};
case actionTypes.SUBMIT_QUERY:
return {
...state,
finalSearchValue: action.finalSearchValue
};
case actionTypes.SET_SEARCH_RESULTS:
return {
...state,
searchResults: action.searchResults
};
case actionTypes.GET_PLAYLIST:
return {
...state,
playlist: action.playlist
};
case actionTypes.UPDATE_PLAYLIST:
const newPlaylist = state.playlist;
newPlaylist.push(action.playlistItem);
return {
...state,
playlist: newPlaylist
};
case actionTypes.SET_INFO_SEARCH_RESULT:
return {
...state,
searchResult: {
id: action.searchResult.id,
artistId: action.searchResult.artistId,
artistName: action.searchResult.artistName,
track: action.searchResult.track,
collectionName: action.searchResult.collectionName,
kind: action.searchResult.kind,
trackPrice: action.searchResult.trackPrice
}
};
case actionTypes.GET_PREV_25:
if (state.sliceStart - 25 >= 0) {
const newSliceStart = state.sliceStart - 25;
const newSliceEnd = state.sliceEnd - 25;
state.sliceStart = newSliceStart;
state.sliceEnd = newSliceEnd;
state.nextDisabled = false;
if (0 <= newSliceStart) {
state.prevDisabled = true;
}
}
return {
...state,
sliceStart: state.sliceStart,
sliceEnd: state.sliceEnd,
prevDisabled: state.prevDisabled,
nextDisabled: state.nextDisabled
};
case actionTypes.GET_NEXT_25:
if (state.sliceEnd < state.searchResults.length) {
const newSliceStart = state.sliceStart + 25;
const newSliceEnd = state.sliceEnd + 25;
state.sliceStart = newSliceStart;
state.sliceEnd = newSliceEnd;
state.prevDisabled = false;
if (state.searchResults.length === newSliceEnd) {
state.nextDisabled = true;
}
}
return {
...state,
sliceStart: state.sliceStart,
sliceEnd: state.sliceEnd,
prevDisabled: state.prevDisabled,
nextDisabled: state.nextDisabled
};
default:
return state;
}
};
export default reducer;
Upvotes: 0
Views: 57
Reputation: 119
Your onClick
handler on the button should have the reference to the handler function, but currently, you are invoking it and onClick
have the value that the method returns(In this case addToPlaylistHandler
returns undefined
)
To fix that you need to either change the onClick to an inline function or wrap your handler into another returning function and thus carrying the data in.
Approach 1: make your onClick an inline function
<button
className={classes.addToPlaylist}
onClick={() => this.addToPlaylistHandler(
this.props.track,
this.props.id,
this.props.artistName,
this.props.collectionName,
this.props.kind,
this.props.trackPrice
)}
>
Add to Playlist
</button>
Approach 2: rewrite your addToPlaylistHandler to carry the arguments in
addToPlaylistHandler(
track,
trackID,
artistName,
collectionName,
kind,
trackPrice
) {
return () => {
// this one will be executed on click
const data = {
track: track,
trackID: trackID,
artistName: artistName,
collectionName: collectionName,
kind: kind,
trackPrice: trackPrice
};
axios
.post("/playlist.json", data)
.then(response => {
// Ideally, you should update the state on sucessful post
this.props.onUpdatePlaylist(data);
console.log(response);
})
.catch(error => {
// and handle the error properly
console.log(error);
});
}
}
If your dev env supports class properties syntax, you can shorten the handler code(no need to bind handler in the constructor:
addToPlaylistHandler = (
track,
trackID,
artistName,
collectionName,
kind,
trackPrice,
) => () => {
// this one will be executed on click
const data = {
track,
trackID,
artistName,
collectionName,
kind,
trackPrice,
};
axios
.post('/playlist.json', data)
.then((response) => {
this.props.onUpdatePlaylist(data);
console.log(response);
})
.catch((error) => {
console.log(error);
});
};
Upvotes: 0
Reputation: 10873
One issue could be that you're directly modifying the state, try this instead:
case actionTypes.UPDATE_PLAYLIST:
return {
...state,
playlist: [...state.playlist, action.playlistItem]
};
Also you're not calling the action properly, you need to wrap it into a function:
<button
className={classes.addToPlaylist}
onClick={() => this.addToPlaylistHandler( // onClick expects a function and not its result
this.props.track,
this.props.id,
this.props.artistName,
this.props.collectionName,
this.props.kind,
this.props.trackPrice
)}
>
Btw, you don't really need to pass all those props as params here, it's easier to access them inside addToPlaylistHandler
.
Upvotes: 3