Nacim Idjakirene
Nacim Idjakirene

Reputation: 1931

Angular 2 ngrx : what is the proper way to immutably update an array of objects in reducers?

In my Angular2 application, I use ngrx to manage states, so when I receive data from the server, I dispatch an action to a reducer.

MyExampleReducer.ts :

export const Reviews: ActionReducer<any> = (state: any[] = [], action: Action) => {
switch (action.type) {
        case GET_REVIEWS:
            return action.payload;
        case ADD_REVIEW :
            return [...state, {review : action.payload, replays : []}];
        case UPDATE_REVIEW:
            return  '' // return what ? 
        case DELETE_REVIEW:
            return state.filter(item => {
                return item.id !== action.payload.id;
            });
        default:
            return state;
    }
};

The problem is when i have to update an item in my Reviews array, what is the best way to do in the redux way ?

Upvotes: 5

Views: 5888

Answers (2)

chrigu
chrigu

Reputation: 826

Assuming that your state is just an array full of reviews you could do the following:

export const Reviews: ActionReducer<any> = (state: any[] = [], action: Action) => {
switch (action.type) {
        case GET_REVIEWS:
            return action.payload;
        case ADD_REVIEW :
            return [...state, {review : action.payload, replays : []}];
        case UPDATE_REVIEW:

            // get an array of all ids and find the index of the required review
            let index = state.map(review => review.id)
                        .indexOf(action.payload.id);

            return [
              ...state.slice(0, index),
              Object.assign({}, state[index], action.payload),
              ...state.slice(index + 1)
            ]
        case DELETE_REVIEW:
            return state.filter(item => {
        return item.id !== action.payload.id;
        });
        default:
            return state;
}

First you need to find the index of the review that should be updated. After that you can create a new array where you replace the object at the index's position.

A great resource for this kind of mutations is this video.

Upvotes: 4

cartant
cartant

Reputation: 58400

You can use map to return an array that has the element that corresponds to the action updated:

export const Reviews: ActionReducer<any> = (state: any[] = [], action: Action) => {
  switch (action.type) {
    case ADD_REVIEW:
      return [...state, { review: action.payload, replays: [] }];
    case UPDATE_REVIEW:
      return state.map(item => item.id === action.payload.id ? { review: action.payload, replays: [] } : item);
    case DELETE_REVIEW:
      return state.filter(item => item.id !== action.payload.id);
    default:
      return state;
    }
}

Also, you can simplify the reviews reducer by using the review reducer to perform the ADD_REVIEW and UPDATE_REVIEW actions - the reviews reducer is then only concerned with managing the list of reviews and not the reviews themselves:

import { reviewReducer } from '...';
export const Reviews: ActionReducer<any> = (state: any[] = [], action: Action) => {
  switch (action.type) {
    case ADD_REVIEW:
      return [...state, reviewReducer(undefined, action)];
    case UPDATE_REVIEW:
      return state.map(item => item.id === action.payload.id ? reviewReducer(item, action) : item);
    case DELETE_REVIEW:
      return state.filter(item => item.id !== action.payload.id);
    default:
      return state;
    }
}

Upvotes: 5

Related Questions