How to add to a state array in react-redux - javascript

kind of stuck on this little tidbit. I'm pretty new to redux so I'm just trying to figure it out. I'm trying to seperate my players array which come in on the response based on the players position. It all works except that the state is just constantly changing to the most recent player in that position instead of adding to the array of players in that position. Here are my actions and reducer:
//action.js
export const FETCH_PLAYERS_SUCCESS = 'FETCH_PLAYERS_SUCCESS';
export const fetchPlayersSuccess = players => ({
type: FETCH_PLAYERS_SUCCESS,
players
});
export const SET_WR = 'SET_WR';
export const setWR = wr => ({
type: SET_WR,
wr
});
export const SET_QB = 'SET_QB';
export const setQB = qb => ({
type: SET_QB,
qb
});
export const SET_RB = 'SET_RB';
export const setRB = rb => ({
type: SET_RB,
rb
});
export const SET_TE = 'SET_TE';
export const setTE = te => ({
type: SET_TE,
te
});
export const fetchPlayers = () => {
return dispatch => {
const proxyurl = "https://cors-anywhere.herokuapp.com/";
const url = "http://api.fantasy.nfl.com/v1/players/stats?statType=seasonStats&season=2017&week=1&format=json"; // site that doesn’t send Access-Control-*
fetch(proxyurl + url)
.then(res => res.json())
.catch(error => {
console.error('Error:', error)
dispatch(fetchPlayersError(error))
})
.then(response => {
let thisPlayer = response.players
for(let i=0; i<thisPlayer.length; i++){
if(thisPlayer[i].position == 'WR'){
dispatch(setWR(thisPlayer[i]))
}
if(thisPlayer[i].position == 'QB'){
dispatch(setQB(thisPlayer[i]))
}
if(thisPlayer[i].position == 'RB'){
dispatch(setRB(thisPlayer[i]))
}
if(thisPlayer[i].position == 'TE'){
dispatch(setTE(thisPlayer[i]))
}
}
dispatch(fetchPlayersSuccess(response))
});
}}
my reducer:
//reducer.js
const initialState = {
players: [],
loading: false,
error: null,
wr: [],
qb: [],
rb: [],
te: []
};
export default (state = initialState, action) => {
if (action.type === FETCH_PLAYERS_REQUEST) {
return Object.assign({}, state, {
loading: true,
error: null
});
}
if (action.type === FETCH_PLAYERS_SUCCESS) {
//console.log(state, action);
return Object.assign({}, state, {
players: action.players,
loading: false
});
}
if (action.type === SET_QB) {
//console.log(state, action);
return Object.assign({}, state, {
qb: action.qb,
loading: false
});
}
if (action.type === SET_RB) {
//console.log(state, action);
return Object.assign({}, state, {
rb: action.rb,
loading: false
});
}
if (action.type === SET_WR) {
//console.log(state, action);
return Object.assign({}, state, {
wr: action.wr,
loading: false
});
}
if (action.type === SET_TE) {
//console.log(state, action);
return Object.assign({}, state, {
te: action.te,
loading: false
});
}
if (action.type === FETCH_PLAYERS_ERROR) {
return Object.assign({}, state, {
loading: false,
error: action.error
});
}
return state;
};
thanks for any help in advance.

I think you are overwriting the state object.
I don't think your code is adding to an array, just overwriting it.
Also, I would pass the array from the response to one dispatch function, instead of looping and calling serval dispatch functions or format the object before you call dispatch. I think that would be easier to debug and maintain. You only need to call dispatch once, unless you have a compelling reason to call it several times.
Use a switch statement for your reducer and update each array only if needed, otherwise just keep the array from the current state.
I see that you are using Object.assign. Instead, try just updating the arrays individually as needed and return a new object for each action.
I don’t know all of your requirements, but this may help:
export const loadPlayers = formattedRespObj => ({
type: 'FETCH_PLAYERS_SUCCESS',
players: formattedRespObj.players,
wr: formattedRespObj.wr,
qb: formattedRespObj.qb,
rb: formattedRespObj.rb,
te: formattedRespObj.te
});
function formatRespObj(playersResp) {
let formattedRespObj = {players: [], wr: [], qb: [], rb: [], te: []};
// Note, this can probably be prettier
for (let i = 0; i < playersResp.length; i++) {
switch (playersResp[i].position) {
case 'WR':
formattedRespObj.wr.push(playersResp[i]);
break;
case 'QB':
formattedRespObj.qb.push(playersResp[i]);
break;
case 'RB':
formattedRespObj.rb.push(playersResp[i]);
break;
case 'TE':
formattedRespObj.te.push(playersResp[i]);
break;
default:
console.error("Unknown player position");
break;
}
}
formattedRespObj.players = [...formattedRespObj.wr, ...formattedRespObj.qb, ...formattedRespObj.rb, ...formattedRespObj.te];
return formattedRespObj;
}
const initialState = {
players: [],
loading: false,
error: null,
wr: [],
qb: [],
rb: [],
te: []
};
export default function reducer(playersState = initialState, action) {
switch (action.type) {
case 'LOAD_PLAYERS':
return {
players: [...playersState.wr, playersState.wr],
loading: true,
error: null,
wr: [...playersState.wr, action.wr],
qb: [...playersState.qb, action.qb],
rb: [...playersState.rb, action.rb],
te: [...playersState.te, action.te],
};
case 'FETCH_PLAYERS_ERROR':
return {
loading: false,
error: action.error,
wr: playersState.wr,
qb: playersState.qb,
rb: playersState.rb,
te: playersState.te
};
default:
return {
loading: false,
error: null,
wr: playersState.wr,
qb: playersState.qb,
rb: playersState.rb,
te: playersState.te
}
}
}
export const fetchPlayers = () => {
return dispatch => {
const proxyurl = "https://cors-anywhere.herokuapp.com/";
const url = "http://api.fantasy.nfl.com/v1/players/stats?statType=seasonStats&season=2017&week=1&format=json"; // site that doesn’t send Access-Control-*
fetch(proxyurl + url)
.then(res => res.json())
.catch(error => {
console.error('Error:', error);
dispatch(fetchPlayersError(error));
})
.then(response => {
let formattedRespObj = formatRespObj(response);
dispatch(loadPlayers(formattedRespObj));
dispatch(fetchPlayersSuccess(response))
});
}
};

Related

Redux Thunk data is stored as a string not as an object

I want store a new group as an object into the groups entity in the store. Everything works perfectly but the new group is stored as an object not as a string. I am using Mockoon to mock an API and the data type is set to be application/json. Can someone explain to me what might be the possible cause of this behavior? I am quite new on using redux so some input would be really appreciated too.
Thank you
const dispatch = useDispatch();
const initialGroupState = {
id: null,
name: "",
description: "",
members: []
}
const [group, setGroup] = useState(initialGroupState)
const [submitted, setSubmitted] = useState(false);
const handleInputChange = event => {
const { name, value } = event.target;
setGroup({ ...group, [name]: value });
};
const saveGroup = (e) => {
e.preventDefault();
const {name, description} = group;
dispatch(createGroup(name, description))
.then(data => {
setGroup({
id: Math.floor(Math.random() * 10000),
name: data.name,
description: data.description,
});
setSubmitted(true);
})
.catch(e => {
console.log(e);
});
}
const newGroup = () => {
setSubmitted(false);
};
My reducer:
const initialState = []
function groupsReducer(groups = initialState, action) {
const { type, payload } = action;
console.log([...groups]);
switch (type) {
case CREATE_GROUP:
return [...groups, payload];
case RETRIEVE_GROUPS:
return payload;
default:
return groups;
}
};
My actions:
export const createGroup = (name, description) => async (dispatch) => {
try {
const res = await GroupDataService.create({ name, description });
dispatch({
type: CREATE_GROUP,
payload: res.data,
});
console.log(res.data)
return Promise.resolve(res.data);
} catch (err) {
console.log(err)
return Promise.reject(err);
}
};

Unhandled Rejection (TypeError): Cannot read property

I am sure that my api is successfully connected with popularGamesURL upcomingGamesURL and newGamesURL
when I try to fetch my api then show me error:
Unhandled Rejection (TypeError): Cannot read property 'popular' of undefined
I have tried this way
gamesAction.js file:
import axios from "axios";
import { popularGamesURL, upcomingGamesURL, newGamesURL } from "../api";
//Action creator
export const loadGames = () => async (dispatch) => {
//Fetch axios
const popularData = await axios.get(popularGamesURL());
const newGamesData = await axios.get(newGamesURL());
const upcomingData = await axios.get(upcomingGamesURL());
dispatch({
type: "FETCH_GAMES",
paylaod: {
popular: popularData.data.results,
upcoming: upcomingData.data.results,
newGames: newGamesData.data.results,
},
});
};
gamesReducer.js file:
const initialState = {
popular: [],
newGames: [],
upcoming: [],
searched: [],
};
const gamesReducer = (state = initialState, action) => {
switch (action.type) {
case "FETCH_GAMES":
return {
...state,
popular: action.payload.popular,
upcoming: action.payload.upcoming,
newGames: action.payload.newGames,
};
default:
return {
...state,
};
}
};
export default gamesReducer;
I'm tried to find error.
Any suggestion please.
You're dispatching an action with a wrong property name of paylaod(note the ao instead of oa) in your gamesAction.js and you're expecting a property named payload in your reducer.
dispatch({
type: "FETCH_GAMES",
paylaod: { // <=== this
popular: popularData.data.results,
upcoming: upcomingData.data.results,
newGames: newGamesData.data.results,
},
});
A simple typo which you should have all the means to debug yourself locally.

Double spread problem - getting rid of Object.assign()

Here is my reducer body code fragment:
const newState = {
...state,
programs: {
...state.programs,
...Object.assign(
{},
...action.payload.map(
(channelName) =>
({
[channelName]: {
...state.programs[channelName],
selected: true
}
})
)
)
}
}
return newState
Is there any chance to get rid of Object.assign in this case?
The classical advice to change Object.assign({}, a) to { ...a } does not work in this case, because here we already have ...action.payload.map, so it would result in ... { ...a } which makes spread to produce array-like keys of 0,1,2...
Is there any elegant way to transform my code correctly?
Ever heard of reduce?
const action = {
payload: ['discoveryChannel']
}
const state = {
programs: {
cartoonNetwork: {
description: '',
when: new Date()
},
discoveryChannel: {
description: '',
when: new Date()
}
}
}
const newState = {
...state,
programs: {
...state.programs,
...action.payload.reduce(
(acc, channelName) => {
acc[channelName] = {
...state.programs[channelName],
selected: true
}
return acc;
}, {})
}
}
console.log(newState);
Another option to use Object.fromEntries:
const action = {
payload: ['discoveryChannel']
}
const state = {
programs: {
cartoonNetwork: {
description: '',
when: new Date()
},
discoveryChannel: {
description: '',
when: new Date()
}
}
}
const newState = {
...state,
programs: {
...state.programs,
...Object.fromEntries(
action.payload.map(
channelName => ([
channelName, {...state.programs[channelName], selected: true}
])
)
)
}
}
console.log(newState);

DOM does not remove item after state update and Delete in ReactJS

I've been baffled as to why an item - card does not disappear from the DOM after deletion and state update. My edit function works fine and I've soured my code for spelling errors and wrong variables, etc.
Here's my App (top component) state object:
state = {
decks: [],
cards: [],
selectedCards: [],
selectedDecks: [],
currentUser: null,
users: []
}
my Delete function (optimistic) in App that gets passed down to a deckLayout component:
deleteCard = (cardId, deckId) => {
const cardCopy = this.state.cards.slice()
const foundOldCardIdx = cardCopy.findIndex(card => card.id === cardId)
cardCopy.splice(foundOldCardIdx, 1)
this.setState({
cards: cardCopy
}, console.log(this.state.cards, cardCopy))
this.filterCards(deckId)
console.log(this.state.cards)
fetch(`http://localhost:9000/api/v1/cards/${cardId}`, {
method: 'DELETE'
})
};
And this is a filterCards functions that gets called after Delete and State update (this works for Edit):
filterCards = (deckId) => {
if (this.state.cards.length === 0) {
alert('No cards yet!')
} else {
const filteredCards = this.state.cards.filter(card => card.deck_id === deckId)
this.setState({
selectedCards: filteredCards
})
this.filterDecks()
}
};
which then calls a filterDecks function:
filterDecks = () => {
if (this.state.currentUser) {
const filteredDecks = this.state.decks.filter(deck => {
return deck.user_id === this.state.currentUser.id
})
this.setState({
selectedDecks: filteredDecks
})
} else {
alert('Login or sign up')
}
};

async/await - dispatching redux actions: dispatching an action causes my app to freeze

I am creating an app using Expo SDK 26 - I am working on a piece where I need to upload a photo into firebase database. For android and ios - Expo provides Image Picker to allow native access to gallery/capturing an image.
When I receive an image - I am trying to capture the image uri, fetch the image and upload it to firebase storage with a reference to the images firebase storage url saved in my firebase database.
When I select the image from a device, my action creator is triggered, but when I dispatch an action - the app stops at the dispatch. Am I missing something obvious, or is the pattern not correct?
If so - what would be a good approach to this.
Select Image From Device Code:
async _pickImage(){
let result = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
aspect: [4, 3],
});
if (!result.cancelled) {
this.props.uploadPhoto(result.uri)
}
};
Upload Image Code:
export const uploadPhoto = (uri) => {
return async (dispatch) => {
dispatch({
type: ActionTypes.UPLOAD_PROFILE_IMAGE_REQUEST
})
const response = await fetch(uri);
const blob = await response.blob();
const currentUser = firebase.auth().currentUser
const userId = currentUser.uid;
const ref = firebase.storage().ref().child(userId);
const databaseRef = firebase.database().ref().child("/users/" + userId + "/user-details")
await ref.put(blob)
.then((snapshot) => {
databaseRef.update({
"photoURL": snapshot.downloadURL
}).then(() => {
console.log(snapshot.downloadURL)
dispatch({
type: ActionTypes.UPLOAD_PROFILE_IMAGE_SUCCESS,
payload: snapshot.downloadURL
})
})
})
.catch((error) => {
dispatch({
type: ActionTypes.UPLOAD_PROFILE_IMAGE_FAILURE,
error: error
})
})
}
}
Reducer: UPLOAD_PROFILE_IMAGE_REQUEST
import * as ActionTypes from '../ActionTypes'
const INITIAL_STATE = {
userInfo: {},
error: "",
isLoading: false,
image: undefined
};
export default (state = INITIAL_STATE , action) => {
switch (action.type) {
case ActionTypes.GET_USER_DETAILS_REQUEST:
return {...state, isLoading: true }
case ActionTypes.GET_USER_DETAILS_SUCCESS:
return {...state, isLoading: false, userInfo: action.payload}
case ActionTypes.GET_USER_DETAILS_FAILURE:
return {...state, isLoading: false, error: action.payload }
case ActionTypes.UPLOAD_PROFILE_IMAGE_REQUEST:
return {...state, isLoading: true}
case ActionTypes.UPLOAD_PROFILE_IMAGE_SUCCESS:
return {...state, image: action.payload, isLoading: false}
case ActionTypes.UPLOAD_PROFILE_IMAGE_FAILURE:
return {...state, error: action.payload, isLoading: false}
case ActionTypes.CLEAR_USER_DETAILS:
return {INITIAL_STATE, isLoading: false}
default:
return state
}
}
I have tried console.log directly after the first dispatch but nothing prints in the log after the UPLOAD_PROFILE_IMAGE_REQUEST action dispatch.
any help is much appreciated.

Categories