PROBLEM STATEMENT
I am trying to modify a array of objects that is stored in redux store. After updating from a component it doesn’t re-render the component.
Basically, I take a redux-state which is a array of objects using mapStateToProps
. Then, update a object in this array from a react component. I expect when the array is manipulated the component will re-render with updated array. But, unfortunately when I update the object of this array, my component can’t detect the changes.
REDUX STATE
const initialState = { basket: [ {id: 1, name: "", quantity: 1}, {id: 2, name: "", quantity: 1}, {id: 3, name: "", quantity: 1}, ], }; // My Reducers const foodReducer = (state = initialState, action) => { ..................... ..................... ..................... }
REACT COMPONENT
Here, increaseItem
function just update the quantity of a item.
Note : When increaseItem
function called, redux-dev-tools shows the changes.
function Ordered({ basket }) { // INCREASE FOOD ITEM const increaseItem = (id) => { basket.map(food => { if(food.id === id){ food.quantity++; } }); useEffect(() => { console.log(basket); }, [JSON.stringify(basket)]); return ( {basket.length > 0 && basket.map((food) => ( <div className="ofood" key={food.id}> <div className="no">{food.id}</div> <div className="name">{food.name}</div> <div className="quantity"> <div className="btn" onClick={() => increaseItem(food.id)}> + </div> <div>{food.quantity}</div> </div> </div> ))} ); } const mapStateToProps = (state) => { return { basket: state.food.basket, }; }; export default connect(mapStateToProps, null)(Ordered);
How can I resolve this issue ????
Advertisement
Answer
food.quantity++
is a mutation of the Redux state. This will cause the value in Redux to change, but the component will not re-render because you mutated the data rather than updating it properly.
As far as React is concerned, basket
hasn’t changed since the array contains the same items as before. You mutated one of those items such that the quantity
is different, but React doesn’t know that. That’s why you had to use JSON.stringify(basket)
in your useEffect
dependencies instead of basket
.
You cannot call food.quantity++
in your reducer either, unless you are using a helper library like Redux Toolkit. Every object that you change must be replaced with a copied version. A non-mutating reducer should look like this:
const foodReducer = (state = initialState, action) => { switch (action.type) { case "INCREASE_QUANTITY": return { ...state, basket: state.basket.map((food) => { if (food.id === action.payload) { return { ...food, quantity: food.quantity + 1 }; } else return food; }) }; } };
With Redux Toolkit, it’s a lot simpler.
export const foodSlice = createSlice({ name: "food", initialState, reducers: { increaseItem(state, action) { state.basket.forEach((food) => { if (food.id === action.payload) { // it's ok to do this with Redux Toolkit food.quantity++; } }); // don't return anything, just modify the draft state } } }); export const {increaseItem} = foodSlice.actions; export default foodSlice.reducer;
import { useEffect } from "react"; import { increaseItem } from "../store/slice"; import { connect } from "react-redux"; function Ordered({ basket, increaseItem }) { useEffect(() => { console.log(basket); }, [JSON.stringify(basket)]); return ( <div> {basket.map((food) => ( <div className="ofood" key={food.id}> <div className="no">{food.id}</div> <div className="name">Food: {food.name}</div> <div className="quantity"> <div className="btn" onClick={() => increaseItem(food.id)}> + Add One </div> <div>Current Quantity: {food.quantity}</div> </div> </div> ))} </div> ); } const mapStateToProps = (state) => { return { basket: state.food.basket }; }; export default connect(mapStateToProps, { increaseItem })(Ordered);