How do I update a state in redux reducer? - reactjs

reducers.js
this is my initialState
const initialState = {
transfusions: [],
};
Here is my Switch logic
export default function (state = initialState, action) {
switch (action.type) {
case GET_TRANSFUSIONS:
return {
...state,
transfusions: action.payload,
};
case ADD_TRANSFUSION:
return {
...state,
transfusions: [...state.transfusions, action.payload],
};
case UPDATE_TRANSFUSION:
console.log(action.payload)
return {
...state,
transfusions: state.transfusions.map(
(transfusion) => transfusion.id === action.payload.id ? {
...state,
transfusion: action.payload
} : transfusions
),
};
case DELETE_TRANSFUSION:
return {
...state,
transfusions: state.transfusions.filter(
(transfusion) => transfusion.id !== action.payload
),
};
default:
return state;
}
}
I updated in the backend successfully but how do I update the frontend. I tried this method it works but it always needs to refresh the page to show me the updated data.

transfusions: state.transfusions.map( is relying on the old state so it is always one step behind
You can modify your code to something like this:
const transfusionExists = state.transfusions.find(t => t.id === action.payload.id)
if(!transfusionExists) state.transfusions.push(action.payload)
return {
...state
),
};

Finally I solved my issue. I used this code:
case UPDATE_TRANSFUSION:
const transfusionsExisted = state.transfusions.filter(
(transfusion) => transfusion.id !== action.payload.id
);
return {
transfusions: [action.payload, ...transfusionsExisted],
};
I deleted the record I wanted to update first and then pushed the new record.
I hope it will help you if you are suffering as I suffered from this problem.

Related

Update one of the objects in array, in an immutable way with immer.js

Say I have some initial state like
const initialState = {
post: { comments: {
{0: {id:'1',name:'myname',statusdata: false}},
{1: {id:'2',name:'yourname',statusdata: true}},
},
};
And I want add to data as the result of an action but the data I want to add is going to be an array. How do I go about this?
export default produce((draft, action) => {
switch (action.type) {
case UPDATE_NAME:
draft.posts.comments.name = action.payload;
break;
case CHANGE_STATUS:
draft.posts.comments.statusdata = !action.payload;
break;
default:
}
}, initialState);
I have this error
Error: [Immer] Immer only supports setting array indices and the 'length' property
I have made assumptions on the schema of comments and action.payload. Try the following solution.
Object.values(draft.post.comments).map((comment){
if(comment.id == action.payload.id){
const updatedComment = Object.extend({}, comment, {"name": action.payload.name});
return updatedComment;
}
return comment;
})
I'm try
export default produce((draft, action) => {
switch (action.type) {
case CHANGE_STATUS:
draft.posts.comments[
draft.posts.comments.findIndex(i => i.id === action.payload)
].statusdata = action.payload;
return draft;
default:
}
}, initialState);

Redux: Action is being called properly but reducer is not updating the state, just keeping the initial state

I have been building an application using React, Redux, Redux-thunk.
While calling action, the payload and type are properly received by the reducer but it is not modifying them.
My Reducer:
import EditorActionTypes from './editor.types';
const INITIAL_STATE = {
editorModified: false,
tabs: [
{
name: 'main.js',
active: true
},
{
name: 'setup.js',
active: false
}
]
};
const editorReducer = ( state = INITIAL_STATE, action ) => {
switch (action.payload) {
case EditorActionTypes.SELECT_TAB:
return {
...state,
tabs: [
...state.tabs.map(
(tab, index) => index === action.payload
? tab.active = true
: tab.active = false
)
]
}
default:
return state;
}
};
export default editorReducer;
Switch condition is wrong,
Reducer function should check for action.type
switch (action.type) {
case EditorActionTypes.SELECT_TAB:
return {
The problematic part, I think, is this:
...state.tabs.map(
(tab, index) => index === action.payload
? tab.active = true
: tab.active = false
)
You are not returning anything from the function inside map. You should return tab objects, instead.
...state.tabs.map(
(tab, index) => ({
...tab,
isActive: index === action.payload
}))
And as #Gangadhar Gandi pointed out, you should switch on the action.type, not the action.payload

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.

Removing Item from array in reducer not working

I am trying to remove single item from cart in reducer but not it does not seems to work. itemsInCart is Updated in ADD_TO_CART but not in REMOVE_FROM_CART.
Can anyone suggest edit to my code....
I tried passing mutable/immutable params to manageItemCount()
function manageItemCount(allItems, newItem){
let itemIndex = [];
if(allItems.length > 0) {
allItems.forEach((elem, i) => {
if (elem.product.id == newItem.product.id) {
itemIndex.push(i);
};
});
if(itemIndex.length){
allItems.splice(itemIndex.length-1, 1);
}
}
return allItems;
}
let alreadyRemovedFromCart = false;
const cartReducer = (state = {
itemsInCart: []
}, action) => {
switch (action.type) {
case 'ADD_TO_CART':
state = {
...state,
itemsInCart: [...state.itemsInCart, action.payload]
};
break;
case 'REMOVE_FROM_CART':
state = {
...state,
itemsInCart: manageItemCount(...state.itemsInCart, action.payload)
};
break;
}
return state;
}
export default cartReducer;
manageItemCount accepts two parameters but you are spreading all the itemsInCart array. So it should be:
case 'REMOVE_FROM_CART':
state = {
...state,
itemsInCart: manageItemCount(state.itemsInCart, action.payload)
};
break
Also manageItemCount seems like it is doing just .filter on itemsInCart.

Changing an array in a reducer when an item has been deleted or updated in react/redux

I am using react/redux and have been looking at ways to update my array of posts in my reducer if a post is edited or deleted.
Is there a more simple way rather than doing something with creating an index function which finds the index of the post you are trying to delete/update, then returning the current state with the updated array using slice?
This is what I have so far in my reducer, I am kind of stuck:
import constants from '../constants';
const initialState = {
all: [],
sorted: [],
};
export default (state = initialState, action) => {
const newState = Object.assign({}, state);
switch (action.type) {
case constants.CREATE_OFFER:
newState[action.payload.id] = action.payload;
newState.all.unshift(action.payload);
return newState;
case constants.GET_OFFERS:
newState.all = action.payload;
return newState;
case constants.GET_OFFER:
newState[action.payload.id] = action.payload;
return newState;
case constants.EDIT_OFFER:
newState[action.payload.id] = action.payload;
return newState;
case constants.DELETE_OFFER:
newState[action.payload.id] = action.payload;
return newState;
case constants.SORT_OFFERS:
newState.sorted = action.payload;
return newState;
default:
return state;
}
};
Instead of :
newState[action.payload.id] = action.payload;
return newState;
You can use ES6 sytax :
return {...state, [action.payload.id]: action.payload.data};
i believe if you use Object as a app state rather than an array , it will be easy to update , delete all you need to do is use .mapKeys method of lodash library along with property you want to extract from your array (ideally 'id')
import _ from 'lodash';
return _.mapKeys(action.payload.data, 'id');
this will return :
{
'1': { id: 1,
title: 'Hello!',
},
'2': {
id: 2,
title: 'World'
}
}
You can read more Here
I think, everything is good, but it will be better to use es6 spread operator, your code will look more clear
case constants.CREATE_OFFER:
return {
...state,
all: state.all.concat(action.payload),
[action.payload.id]: action.payload
}
case constants.EDIT_OFFER:
return {
...state,
[action.payload.id]: action.payload,
all: state.all.map( item => item.id === action.payload.id?action.payload: item )
}
case constants.DELETE_OFFER:
const new = {
...state,
all: state.all.filter( item => item.id !== action.payload.id)
};
delete new[action.payload.id];
return new;
}

Resources