191

How do I add elements in my array arr[] of redux state in reducer? I am doing this-

import {ADD_ITEM} from '../Actions/UserActions'
const initialUserState = {
    arr:[]
}

export default function userState(state = initialUserState, action)
{
    console.log(arr);
    switch (action.type)
    {
        case ADD_ITEM: 
            return { 
                      ...state,
                      arr: state.arr.push([action.newItem])
                   }

        default:
            return state
    }
}
falinsky
  • 7,229
  • 3
  • 32
  • 56
coderzzz18
  • 2,535
  • 5
  • 16
  • 23

9 Answers9

466

Two different options to add item to an array without mutation

case ADD_ITEM :
    return { 
        ...state,
        arr: [...state.arr, action.newItem]
    }

OR

case ADD_ITEM :
    return { 
        ...state,
        arr: state.arr.concat(action.newItem)
    }
yadhu
  • 15,423
  • 7
  • 32
  • 49
  • 2
    Which one should be considered preferable? – sapht May 15 '17 at 10:19
  • 22
    @sapht if you're writing code in ES6 the prefer the first one which is more readable and elegant IMO. – yadhu May 15 '17 at 12:05
  • 8
    Also, the first one is pure, the second isn't. From the Redux docs: "It's very important that the reducer stays pure. Things you should never do inside a reducer: 1. Mutate its arguments; 2. Perform side effects like API calls and routing transitions; 3. Call non-pure functions, e.g. Date.now() or Math.random()." – Charming Robot Feb 18 '19 at 13:43
  • Hello, @YadhuKiran can u explain to me why we add new value in the second index of the array i could not understand it very well? `arr: [...state.arr, why here?]` – Oliver D Jan 29 '20 at 09:34
  • @OliverD, When [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) is used as above, it is not inserting element at the second index, but at the last index. In contrast to this, `[action.newItem, ...state.arr]` would insert the element at the first position. – yadhu Jan 29 '20 at 10:13
  • @YadhuKiran what's the difference if it in first index or last index? – Oliver D Jan 30 '20 at 13:16
  • 3
    @CharmingRobot How exactly is the second function not pure? 1. It doesn't mutate the arguments; 2: it doesn't have any side effects; 3. Array.prototype.concat is a pure function; – Jawi Feb 03 '20 at 20:38
  • @Jawi Does `state.arr.concat(action.newItem)` get converted to `Array.prototype.concat(state.arr, action.newItem)` by the interpreter? – Charming Robot Feb 11 '20 at 00:54
  • @CharmingRobot I'm not as knowledgeable as some about the interpreter but as a method of the Array prototype, Array.prototype.concat(action.newItem) is the function being called when you use state.arr.concat(action.newItem). https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat – Jawi Feb 11 '20 at 15:17
  • 3
    @CharmingRobot concat() doesn't mutate the original array. – Rob Evans Dec 21 '20 at 20:12
26

push does not return the array, but the length of it (docs), so what you are doing is replacing the array with its length, losing the only reference to it that you had. Try this:

import {ADD_ITEM} from '../Actions/UserActions'
const initialUserState = {

    arr:[]
}

export default function userState(state = initialUserState, action){
     console.log(arr);
     switch (action.type){
        case ADD_ITEM :
          return { 
             ...state,
             arr:[...state.arr, action.newItem]
        }

        default:return state
     }
}
martinarroyo
  • 9,389
  • 3
  • 38
  • 75
  • 1
    Can anyone answer this question with `Object.assign` for the changes of the array inside the state please? – Nesa Mouzehkesh Mar 16 '18 at 03:01
  • 2
    Why do you need `Object.assign`? It is used for objects, and it is essentially the same thing. Where you have `arr:[...state.arr, action.newItem]` you can use `obj: Object.assign({}, state.obj, action.newItem`, provided that `obj` and `newItem` are objects. If you want to do that to the entire state, you can just do `Object.assign({}, state, {arr: [..state.arr, action.newItem]})` – martinarroyo Mar 17 '18 at 22:28
  • 1
    @martinarroyo, thanks so I did: `var obj = { isLoading: true, data: ['a', 'b', 'c', 'd'] } var newObj = Object.assign({}, obj, { data: [...obj.data, 'e'] });` and it gives me this: `{ isLoading: true, data: [ 'a', 'b', 'c', 'd', 'e' ] }` for newObj which is good enough for what I need. But I wanted to use Object.assign without the spread operator to produce the same result. Is that possible? – Nesa Mouzehkesh Mar 20 '18 at 04:33
  • 1
    @Vennesa I guess that you can replace it with `[].concat(obj.data, ['e'])`, if you don't want to use ES6 features. Object.assign is meant for objects, and although it won't give you any errors to use it on an array, the result is not a concatenation of the arrays. If you try `Object.assign({}, [1, 2, 3], [4])`, you get `[4, 2, 3]` as a result. – martinarroyo Mar 20 '18 at 15:26
22

If you need to insert into a specific position in the array, you can do this:

case ADD_ITEM :
    return { 
        ...state,
        arr: [
            ...state.arr.slice(0, action.pos),
            action.newItem,
            ...state.arr.slice(action.pos),
        ],
    }
JoeTidee
  • 24,754
  • 25
  • 104
  • 149
  • 2
    I stumbled here... though this answer does not exactly fit the question above. Nevertheless, it was exactly what I was looking for. I was looking for a way to insert an object in a specific index in an array. Hence, I voted the answer up. It was very helpful and save me some time. Thanks! – omostan Jun 06 '20 at 02:14
11

Since this question gets a lot of exposure:

If you are looking for the answer to this question, there is a good chance that you are following a very outdated Redux tutorial.

The official recommendation (since 2019) is to use the official Redux Toolkit to write modern Redux code.

Among other things, that will eliminate string action constants and generate action creators for you.

It will also employ methods that allow you to just write mutating logic in your Reducers created by createReducer or createSlice, so there is no need to write immutable code in Reducers in modern Redux in the first place.

Please follow the official Redux tutorials instead of third-party tutorials to always get the most up-to-date information on good Redux practices and will also show you how to use Redux Toolkit in different common scenarios.

For comparison, in modern Redux this would look like

const userSlice = createSlice({
  name: "user",
  initialState: {
    arr:[]
  },
  reducers: {
    // no ACTION_TYPES, this will internally create a type "user/addItem" that you will never use by hand. You will only see it in the devTools
    addItem(state, action) {
      // you can use mutable logic in createSlice reducers
      state.arr.push(action.payload)
    }
  }
})

// autogenerated action creators
export const { addItem } = slice.actions;

// and export the final reducer
export default slice.reducer;
phry
  • 35,762
  • 5
  • 67
  • 81
  • @coderzzz18 it would be great if you could mark this as the answer since the one currently marked is technically correct, but might steer people following it into a style of Redux we don't really recommend learning/teaching any more. – phry Mar 20 '21 at 11:48
  • Does not work with objects. I can push anything into the array except objects. – Kaj Risberg Jul 18 '22 at 09:57
  • @KajRisberg RTK does not differentiate between any values you could be pushing in there. It sounds like you are doing something wrong that might warrant asking it's own question - piggy-backing in this will probably not help you. – phry Jul 18 '22 at 10:22
  • Thank you phry. I will ask own question and see what i am doing wrong. – Kaj Risberg Jul 18 '22 at 10:26
6

If you want to combine two arrays, one after another then you can use

//initial state
const initialState = {
   array: [],
}

...
case ADD_ARRAY :
    return { 
        ...state,
        array: [...state.array, ...action.newArr],
    }
//if array = [1,2,3,4]
//and newArr = [5,6,7]
//then updated array will be -> [1,2,3,4,5,6,7]
...

This Spread operator (...) iterates array element and store inside the array [ ] or spreading element in the array, what you can simply do using "for loop" or with any other loop.

Vishal Pawar
  • 1,447
  • 8
  • 10
2

The easiest solution to nested arrays is concat():

case ADD_ITEM: 
    state.array = state.array.concat(action.paylod)
    return state

concat() spits out an updated array without mutating the state. Simply set the array to the output of concat() and return the state.

Cristian
  • 495
  • 2
  • 9
  • 35
1

I have a sample

import * as types from '../../helpers/ActionTypes';

var initialState = {
  changedValues: {}
};
const quickEdit = (state = initialState, action) => {

  switch (action.type) {

    case types.PRODUCT_QUICKEDIT:
      {
        const item = action.item;
        const changedValues = {
          ...state.changedValues,
          [item.id]: item,
        };

        return {
          ...state,
          loading: true,
          changedValues: changedValues,
        };
      }
    default:
      {
        return state;
      }
  }
};

export default quickEdit;
0

This worked for me

//Form side
const handleSubmit = (e) => {
e.preventDefault();
let Userdata = { ...userdata, id: uuidv4() };
dispatch(setData(Userdata));
};


//Reducer side
const initialState = {
data: [],
};

export const dataReducer = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.SET_DATA:
  return { ...state, data: [...state.data, action.payload] };

default:
  return state;
}
};
0

For me, my redux store looked like this:

export const cartSlice = createSlice({
  name: "cart",
  initialState: {
    itemsAdded: [],
    total: 0,
  },
  reducers: {
    addItem: (state, action) => {action.payload };
      state.itemsAdded.push(action.payload);
    },
  },
});

Simply i added to my array, itemsAdded, by state.itemsAdded.push(action.payload);

Awshaf Ishtiaque
  • 441
  • 5
  • 11