Redux shopping cart example - reactjs

I'am trying to understand the example named "shopping cart" giving for redux :
https://github.com/reactjs/redux/tree/master/examples/shopping-cart
In this example you can add elements to your list of items, I tried to implement the function of remove a list of items :
But in the reducers folder there is an addedIds() function, I added a case to remove the element of the list but I don't know how to implement that, here is the function : the reste of my code is working fine I just don't know how to delete the product id from the array of addedIds.
const initialState = {
addedIds: [],
quantityById: {}
};
function addedIds(state = initialState.addedIds, action) {
switch (action.type) {
case ADD_TO_CART:
console.log("added ADD");
if (state.indexOf(action.productId) !== -1) {
return state
}
return [ ...state, action.productId ];
case REMOVE_TO_CART:
console.log("removed ADD");
// here is my problem
default:
return state
}
}
I assumed I need to do something like here :
Is this the correct way to delete an item using redux?
but I don't know how
Can you help me please ?

You can delete some element from array just filtering it out:
// ... skipped other cases from the switch
case REMOVE_TO_CART:
return state.filter(productId => action.productId !=== productId)
Approach with .filter() function looks very short and produces a new array instance as it required by redux.

For those who have a similar problem here is the solution :
const initialState = {
addedIds: [],
quantityById: {}
};
function addedIds(state = initialState.addedIds, action) {
switch (action.type) {
case ADD_TO_CART:
console.log("added ADD");
if (state.indexOf(action.productId) !== -1) {
return state
}
return [ ...state, action.productId ];
case REMOVE_TO_CART:
console.log("removed ADD");
return [ ...state.slice(0,state.indexOf(action.productId),
...state.slice(state.indexOf(action.productId)+1))
];
default:
return state
}
}
thanks to Josh Deeden who found this vidéo :
https://egghead.io/lessons/javascript-redux-avoiding-array-mutations-with-concat-slice-and-spread

Related

Updating State for changed item not displaying on react frontend

I am working on my reducer and I am trying to get the case for UPDATE working.
I am just trying to update the list on my react frontend when a certain object ID changes in the state. How can I make it so the item with a certain ID changes on the frontend in this reducer.
I tried this in UPDATE_ANIMALS, but no luck
case 'UPDATE_ANIMALS':
return [
...state, item => item.id === action.payload.id ? action.payload : item
];
Below is my entire reducer.
export const ANIMALSReducer = (state = [], action) => {
switch (action.type) {
case 'FETCH_ANIMALS':
return action.payload;
case 'ADD_ANIMALS':
return [...state, action.payload];
case 'DELETE_ANIMALS':
return [
...state.filter(item => item.id !== action.payload)
];
case 'UPDATE_ANIMALS':
return [
NOT SURE WHAT TO PUT HERE
];
default:
return state;
}
};
You can achieve that very easily using the map function, basically you're generating a new array and the matching item given your payload will be updated with the payload instead the original:
case 'UPDATE_ANIMALS':
return state.map(item => item.id === action.payload.id ? action.payload : item);

Redux reducer isn't returning the updated state to UI

I have a reducer case, and this case is supposed to add a comment, and update the UI with the new comment,
Pretty much you add a comment, and it will show under the rest of comments once you do, the logic is not returning the new state for some reason.
What could be the cause of this ? And to be honest im unsure of what im doing, im fairly new when it comes to reducer normalizer state updates.
Only on refresh i see the new comment.
const initialState = {
allIds:[],
byId:{},
};
const allIds = (state = initialState.allIds, action) => {
switch (action.type) {
case FETCH_IMAGES_SUCCESS:
return action.images.reduce((nextState, image) => {
if (nextState.indexOf(image.id) === -1) {
nextState.push(image.id);
}
return nextState;
}, [...state]);
case UPLOAD_IMAGE_SUCCESS:
console.log(action.data)
return [action.data.id, ...state];
case POST_COMMENT_SUCCESS:
console.log(action)
return [action.data.id, ...state];
default:
return state;
}
}
const image = (state = {}, action) => {
switch (action.type) {
case POST_COMMENT_SUCCESS:
return [...state.comments, action.data, ...state.comments]
default:
return state;
}
}
const byId = (state = initialState.byId, action) => {
switch (action.type) {
case FETCH_IMAGES_SUCCESS:
return action.images.reduce((nextState, image) => {
nextState[image.id] = image;
return nextState;
}, {...state});
case POST_COMMENT_SUCCESS:
console.log(action.data) // renders new commnent
return {
...state,
...state.comments,
[action.data.id]: action.data,
}
case UPLOAD_IMAGE_SUCCESS:
console.log(action)
return {
...state,
[action.data.id]: action.data,
};
default:
return state;
}
}
Not sure because I don't see the shape of your byId object, but I think the problem might be here:
case POST_COMMENT_SUCCESS:
return {
...state, // here you are spreading the whole state object
// and here you are spreading state.comments at the same level, NOT nested:
...state.comments,
// Again same thing here, not nested:
[action.data.id]: action.data,
}
Instead you should be doing something like this, more info in the Redux docs:
case POST_COMMENT_SUCCESS:
return {
...state,
comments: {
...state.comments,
[action.data.id]: action.data
}
}
Alternatively, you could use Immer to simplify your reducers, I'm using it and I'm loving it. It feels a little bit weird because you can use mutation methods to modify its draft but it is great if you want to have simpler reducers. Your code with Immer would be something much more simpler:
case POST_COMMENT_SUCCESS:
draft.comments[action.data.id]: action.data,
return;
With Immer you just have to modifiy (or mutate) the draft object, if the value you are assigning to is different from the one you have in state, it will generate a new object for you, otherwise it will return state.
Hope it helps.

Redux adds duplicate array item when loaded from state

Hey everyone probably a simple question, basically I have a button when i click it fires an action and passes down the whole object that I concat to array if its not duplicate but strangely what happens because I save data to local-storage and after I load it from there it does not check for duplicate and duplicates the array item. My reducer code below maybe the error is there?
Searched as much as possible.
const initialState = {
favourites: []
};
const favourites = (state = initialState, action) => {
const { payload } = action;
switch (action.type) {
case actionTypes.ADD_FAVOURITES:
return {
...state,
favourites:
state.favourites.indexOf(payload) === -1
? state.favourites.concat(payload)
: state.favourites
};
default:
return state;
}
};
The issue here seems to be that state.favourites.indexOf(payload) === -1 is always true. This is because the function Array.prototype.findIndex() does not find identical objects.
You should use an alternate method of checking to see if the payload object is already in the favourites array. For example, you could try something like this:
const favourites = (state = initialState, action) => {
const { payload } = action;
switch (action.type) {
case actionTypes.ADD_FAVOURITES:
return {
...state,
favourites:
JSON.stringify(state.favourites).indexOf(JSON.stringify(payload)) === -1
? state.favourites.concat(payload)
: state.favourites
};
default:
return state;
}
};

How to update 1 item in state with redux?

I have a reducer and trying to update 1 item in the statearray. It looks like this:
const players = (state = [{name:'John',nrGames:0,nrWins:0},{name:'Ed',nrGames:0,nrWins:0},{name:'Mark',nrGames:0,nrWins:0}], action) => {
switch (action.type) {
case 'ADD_PLAYER':
return [...state,{name:action.name}]
case 'ADD_WIN':
return [...state, action.name == 'bla' ? {nrWins:10} : {} ]
default:
return state;
}
};
export default players;
I am trying to figure out how to change the nrWins property for a certain name. So when ADD_WIN is dispatched with name='John' how to just update the John object and up the nrWins property with 1 and not the other objects in the state?
You need to .map over the players and when you find the one the action is describing create a new player with the updated number of wins. For readability I created a function called incWinForPlayer.
const incWinForPlayer = (name) => (player) => {
return player.name === name
? {...player, nrWins: player.nrWins + 1}
: player
};
const players = (state, action) => {
switch (action.type) {
case 'ADD_PLAYER':
return [...state, {name: action.name}]
case 'ADD_WIN':
return state.map(incWinForPlayer(action.name));
default:
return state;
}
};
export default players;
The "recommended" approach is to slice the old array up to the new item, concat it with the modified one, and concat it with the rest of the array.
Make sure that your action returns the whole new item, and the index you want to modify.
Something like:
case 'ADD_WIN':
return [
...array.slice(0, action.index),
action.item,
...array.slice(action.index)
];
Edit 1: source https://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html#inserting-and-removing-items-in-arrays

How do I add data from one reducer to existing data in the store?

When a user searches in my ReactJS app he gets results. When the user scrolls I want to load in the next set of data in an infinite scroll using react-waypoint.
What works is the following:
loading the initial set of items (and showing them)
triggering the waypoint scroll
incrementing the page counter on scroll
loading the next page with item from the api into nextItems on scroll
What I can't get working is appending this to the existing items.
What I have now is a seperate reducer which loads all the extra data into nextItems. But I want to simply add this to the existing items. I've tried the following in my items.js reducers. But all this does is add an undefined entry to the nextItems array. Somehow I can't wrap my head around this.
export function items(state = [], action) {
switch (action.type) {
case 'ITEMS_FETCH_DATA_SUCCESS':
return action.items;
default:
return state;
}
}
export function nextItems(state = [], action) {
switch (action.type) {
case 'ITEMS_FETCH_NEXT_DATA_SUCCESS':
return [...state.items, ...action.nextItems];
default:
return state;
}
}
I have the feeling that I have to combine both reducers and split them on type, or fix this in the item.js actions file. But I don't know how to get this working, and everything I tried got me either confusing errors, or nothing at all.
My actions: https://pastebin.com/zEPsxhb8
My reducers: https://pastebin.com/dSNFFff0
You should be able to acheive this with Array.prototype.concat.
export function nextItems(state = [], action) {
switch (action.type) {
case 'ITEMS_FETCH_NEXT_DATA_SUCCESS':
return Object.assign({},
state.items,
state.items.concat(action.nextItems)
);
default:
return state;
}
}
You can use my function. For example you can move this function to Utils file or other service.
{ ... // this is your reducer
return mergeObjectArrays(state, items, options.key || 'id');
}
mergeObjectArrays(arr1, arr2, key = 'id') {
const res = arr1.slice();
for (let item of arr2) {
let index;
if (typeof key === 'string') {
index = arr1.findIndex((itm) => itm[key] == item[key]);
}
else {
index = arr1.findIndex((itm) => {
let eq = true;
for (let i = 0; i < key.length; i++) {
eq = eq && itm[key[i]] == item[key[i]]
}
return eq;
});
}
if (index === -1) res.push(item);
else res[index] = item;
}
return res;
}

Resources