Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 ?

like image 449
Nacim Idjakirene Avatar asked Jan 10 '17 15:01

Nacim Idjakirene


People also ask

How do I change a reducer value?

First, we will find the index of the item in the array using findIndex() . Then we make a copy of the todos array from the state. Then we can change the value in the new array using the index value we got from findIndex() . Lastly, in the return, we reassign todos to that new array.

What is the use of reducer in NgRx?

Reducers in NgRx are responsible for handling transitions from one state to the next state in your application. Reducer functions handle these transitions by determining which actions to handle based on the type.

How do you update objects in Redux?

Redux: Update an Object When you want to update the top-level properties in the Redux state object, copy the existing state with ... state and then list out the properties you want to change, with their new values.

What is NgRx payload?

This is a normal convention with NgRx. The payload is what the store is going to update itself to be. If the payload only consists of one value, it's often convention to not include a payload wrapper and just return the value (i.e. action. locationData instead of action. payload.


2 Answers

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;
    }
}
like image 77
cartant Avatar answered Sep 27 '22 19:09

cartant


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.

like image 42
chrigu Avatar answered Sep 27 '22 21:09

chrigu