Skip to content
Advertisement

Update a redux array of object but not re-render the component

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);

Code Sandbox Demo

Advertisement