React Use Reducer Firing twice - reactjs

My issue is when Im' trying to toggle boolean value within my useReducer function, by doing this it is causing the issue of the value changing back to the original:
function reducerBalls(state: any, action: any) {
let newState;
let item;
switch (action.type) {
case ACTIONS.INIT:
return action.balls;
case ACTIONS.SELECTED:
newState = [...state];
item = newState[action.index] ;
item.active = !item.active;
return newState;
default:
return state;
}}
Here is the dispatch event
function ballCheckboxHandler(ball: lotteryBalls, event: any) {
if(event.target.checked) {
return dispatch({type: ACTIONS.SELECTED, index: ball.number});
}
if(event.target.checked === false) {
return dispatch({type: ACTIONS.UNSELECTED, index: ball.number});
}
}
Now I know react.StrictMode is causing this and live mode they say this issue wont happen, but the problem comes down to the development of it.

You are shallow cloning the array, but then you mutate the actual object, so the item itself doesn't re-render.
Clone the object newState[action.index] by using spread, and change the active property:
function reducerBalls(state: any, action: any) {
switch (action.type) {
case ACTIONS.INIT:
return action.balls;
case ACTIONS.SELECTED:
const newState = [...state];
newState[action.index] = {
...newState[action.index],
active: !newState[action.index].active
};
return newState;
default:
return state;
}
}
I would also change the way that the action works to make it a bit simpler:
function reducerBalls(state: any, action: any) {
switch (action.type) {
case ACTIONS.INIT:
return action.balls;
case ACTIONS.SELECTED:
const newState = [...state];
newState[action.index] = {
...newState[action.index],
active: action.selected // use the selected value
};
return newState;
default:
return state;
}
}
function ballCheckboxHandler(ball: lotteryBalls, event: any) {
return dispatch({
type: ACTIONS.SELECTED,
index: ball.number,
selected: event.target.checked // selected is the checked state of the event
});
}

Related

Update the Old and New Value in Redux

I have a problem updating the old value of the array in my redux react app. I have successfully updated the new selected object true. I want to other object to set to false since I have set the another object to true.
const initialState = {
annualPlans: [],
};
const planReducer = (state = initialState, action) => {
switch (action.type) {
case planConstants.UPGRADE_PLAN_SUCCESS:
return {
...state,
annualPlans: state.annualPlans.map((todo) =>
todo.value === action.data.plan_id
? // transform the one with a matching id
{ ...todo, current: true }
: // otherwise return original todo
todo
),
};
default:
return state;
}
};
It seems like you want to return current set to false for the others:
todo.value === action.data.plan_id
? // transform the one with a matching id
{ ...todo, current: true }
: // otherwise return with current false
{ ...todo, current: false }
Id first create the new todos by looping though a map and then assign to the state
case planConstants.UPGRADE_PLAN_SUCCESS: {
const newTodos = state.annualPlans.map((todo) => {
if (todo.value === action.data.plan_id) {
return { ...todo, current: true }; // if todo.id matched then set the current to true and return;
}
if (todo.current) {
return { ...todo, current: false }; // else if current is already true, then false it and return
}
return todo; // else return original todo
});
return {
...state,
annualPlans: newTodos
};
}
.....
This will optimize the rendering and prevent of looping multiple times.
No need to re create all todo items, if you use pure components then that will mess up this optimisation. You can just add a map to deal with resetting the current value:
const planReducer = (state = initialState, action) => {
switch (action.type) {
case planConstants.UPGRADE_PLAN_SUCCESS:
return {
...state,
annualPlans: state.annualPlans
.map(
(todo) =>
todo.current
? { ...todo, current: false } //reset current to false
: todo // no need to change this one
)
.map((todo) =>
todo.value === action.data.plan_id
? // transform the one with a matching id
{ ...todo, current: true }
: // otherwise return original todo
todo
),
};
default:
return state;
}
};

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