2

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 ????

6
  • 3
    Here, You need to update the basket state in the reducer using actions. Commented Apr 2, 2021 at 10:12
  • 4
    There’s in-place mutation going on and no action is being dispatch at the same time. To make it work, it should be the opposite of that: an action should be dispatched with the new value, and no mutation of the state should happen for Redux to pick up the change and trigger the component render (see official docs). Check out this post as well: stackoverflow.com/questions/41469157/… Commented Apr 2, 2021 at 10:16
  • As @saksh73 said, you need to create a reducer to update the redux state. Genrally those state update actions are exposed to your components via actions. Please check this example on the redux docs. Its pretty thorough in terms of how to achieve the above. redux.js.org/introduction/examples#shopping-cart Commented Apr 2, 2021 at 10:18
  • @saksh73 Actually, the process I described above successfully updated the redux state. This is why I don't use actions. UPDATE: I update the basket state in reducer using action also. Unfortunately It also update my basket state but not re-render like before. Do you please elaborate your answer?? Commented Apr 2, 2021 at 10:20
  • 1
    @Mizanur - Could you please show how are you updating the state in your reducer. Commented Apr 2, 2021 at 10:22

1 Answer 1

4

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.