Is this the correct way to delete an item using redux?
Asked Answered
A

8

143

I know I'm not supposed to mutate the input and should clone the object to mutate it. I was following the convention used on a redux starter project which used:

ADD_ITEM: (state, action) => ({
  ...state,
  items: [...state.items, action.payload.value],
  lastUpdated: action.payload.date
})

for adding an item - I get the use of spread to append the item in the array.

for deleting I used:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: [...state.items.splice(0, action.payload), ...state.items.splice(1)],
  lastUpdated: Date.now() 
})

but this is mutating the input state object - is this forbidden even though I am returning a new object?

Anastomosis answered 3/1, 2016 at 23:7 Comment(3)
Quick question. Splice returns the items that you removed. Is that your intention? If not you should be using slice, which also abides by the no mutations law.Abad
Well in this example I'm splicing the two sections of the array together into a new array - with the item I wanted to remove left out. Slice also returns the removed item right? Only it does so without mutating the original array so that would be the better approach?Anastomosis
@AR7 as per your suggestion: items: [...state.items.slice(0, action.payload.value), ...state.items.slice(action.payload.value + 1 )] using slice now instead of splice so as to not mutate the input - is this the way to go or is there a more concise way?Anastomosis
L
241

No. Never mutate your state.

Even though you're returning a new object, you're still polluting the old object, which you never want to do. This makes it problematic when doing comparisons between the old and the new state. For instance in shouldComponentUpdate which react-redux uses under the hood. It also makes time travel impossible (i.e. undo and redo).

Instead, use immutable methods. Always use Array#slice and never Array#splice.

I assume from your code that action.payload is the index of the item being removed. A better way would be as follows:

items: [
    ...state.items.slice(0, action.payload),
    ...state.items.slice(action.payload + 1)
],
Lupelupee answered 3/1, 2016 at 23:32 Comment(5)
this doesn't work if we're dealing with the last element in the array, also the use of ... in the second statement will double the content of your stateBattology
Please prove that with a jsfiddle/codepen example. The snippet arr.slice(arr.length) should always produce an empty array no matter what the contents of arr.Lupelupee
@david-l-walsh sorry for the confusion, I must've made a typo or something when testing this example. It works wonders. My only question is: why the need of the spread operator ... in the second part - ...state.items.slice(action.payload + 1)Battology
Array#slice returns an array. To combine the two slices into a single array, I've used the spread operator. Without it, you would have an array of arrays.Lupelupee
that makes sense. Thank you very much for clarifying (and sorry for the confusion at first).Battology
G
192

You can use the array filter method to remove a specific element from an array without mutating the original state.

return state.filter(element => element !== action.payload);

In the context of your code, it would look something like this:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => item !== action.payload),
  lastUpdated: Date.now() 
})
Ginoginsberg answered 3/1, 2017 at 16:22 Comment(3)
Does the filter produce a new array?Leyva
@Leyva Yes, the Array.filter method returns a new array. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Ginoginsberg
Note that if there are duplicates, this will remove ALL of them. To use filter to remove a specific index, you can use e.g. arr.filter((val, i) => i !== action.payload )Tarweed
A
27

The ES6 Array.prototype.filter method returns a new array with the items that match the criteria. Therefore, in the context of the original question, this would be:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => action.payload !== item),
  lastUpdated: Date.now() 
})
Argol answered 15/6, 2017 at 23:33 Comment(1)
.filter() is not an ES2015 method, but has been added in the prior version ES5.Crib
T
12

Another one variation of the immutable "DELETED" reducer for the array with objects:

const index = state.map(item => item.name).indexOf(action.name);
const stateTemp = [
  ...state.slice(0, index),
  ...state.slice(index + 1)
];
return stateTemp;
Tyrothricin answered 29/5, 2018 at 0:50 Comment(0)
D
2

Deleting an item using redux in different ways.

Method 1: In that case is used createSlice( .. )

const { id } = action.payload; // destruct id
removeCart: (state, action) =>{
                 let { id } = action.payload;
                 let arr = state.carts.filter(item => item.id !== parseInt(id))
                 state.carts = arr;
               }

Method 2: In that case is used switch (... ), spread-operator

const { id } = action.payload; // destruct id

case actionTypes.DELETE_CART:  
     return {
        ...state,
        carts: state.carts.filter((item) => item.id !== payload)
      };

For both methods initialized this state:

  initialState: {
      carts: ProductData, // in productData mocked somedata     
    }
Deanedeaner answered 9/11, 2021 at 16:1 Comment(0)
E
0

The golden rule is that we do not return a mutated state, but rather a new state. Depending on the type of your action, you might need to update your state tree in various forms when it hits the reducer.

In this scenario we are trying to remove an item from a state property.

This brings us to the concept of Redux’s immutable update (or data modification) patterns. Immutability is key because we never want to directly change a value in the state tree, but rather always make a copy and return a new value based on the old value.

Here is an example of how to delete a nested object:

// ducks/outfits (Parent)

// types
export const NAME = `@outfitsData`;
export const REMOVE_FILTER = `${NAME}/REMOVE_FILTER`;

// initialization
const initialState = {
  isInitiallyLoaded: false,
  outfits: ['Outfit.1', 'Outfit.2'],
  filters: {
    brand: [],
    colour: [],
  },
  error: '',
};

// action creators
export function removeFilter({ field, index }) {
  return {
    type: REMOVE_FILTER,
    field,
    index,
  };
}

export default function reducer(state = initialState, action = {}) {
  sswitch (action.type) {  
  case REMOVE_FILTER:
  return {
    ...state,
    filters: {
    ...state.filters,
       [action.field]: [...state.filters[action.field]]
       .filter((x, index) => index !== action.index)
    },
  };
  default:
     return state;
  }
}

To understand this better, make sure to check out this article: https://medium.com/better-programming/deleting-an-item-in-a-nested-redux-state-3de0cb3943da

Euchromosome answered 16/4, 2020 at 16:28 Comment(0)
A
0

To immutably / functionally remove the element at index from an array:

items: [
    ...items.slice(0, index),
    ...items.slice(index + 1)
]

Or simpler (and possibly faster):

items: items.filter((_, i) => i !== index)
Angelo answered 20/6, 2023 at 12:42 Comment(0)
C
0

Besides the state mutation question, that already has been answered, I'd like to note the new possible ways to operate immutably on arrays with the new array methods added in ES2023:

DELETE_ITEMS: (state, action) => ({
  ...state,
  items: state.items.toSpliced(action.payload.index, action.payload.deleteCount),
})

UPDATE_ITEM: (state, action) => ({
  ...state,
  items: state.items.with(action.payload.index, action.payload.newItem),
})

Also you could do much more complicated operations using toSpliced, since you can add items to be inserted in the index provided as first argument.

Chalcedony answered 3/5, 2024 at 10:14 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.