Bulk updating normalized Redux state

Viewed 887

Let's say I have normalized state like this (using normalizr)

entities: {
  todos: {
    'aaa': {
      id: 'aaa',
      text: 'Todo A',
      completed: false
    },
    'bbb': {
      id: 'bbb',
      text: 'Todo B',
      completed: false
    },
    'ccc': {
      id: 'ccc',
      text: 'Todo C',
      completed: false
    }
  }
}

Then I have an action that fetches array of IDs from the server, that are completed. For that action type, I have reducer like this:

const todos = (state = initialState, action) => {
  switch (action.type) {
    case ActionTypes.FETCH_COMPLETED_SUCCESS:
      return {
        ...state,
        todos: action.payload.completedIds.map((id) => {
          return {
            [id]: {
              ...state.todos[id],
              completed: true
            }
          }
        })
      };
    default:
      return state;
  }
};

If I recieved an array with ['aaa', 'ccc'] (could be thousands of items in real world app), I want to set "completed" to TRUE on those respective todos in a single action, is it possible?

My current implementation of reducer doesn't work since it returns an array of objects, while original normalized state is object with IDs as key.

Thanks.

3 Answers

You could store the modified todos in an object and then update it using the spread syntax like

const todos = (state = initialState, action) => {
  switch (action.type) {
    case ActionTypes.FETCH_COMPLETED_SUCCESS:
      let newTodos = {};
      action.payload.completedIds.forEach((id) => {
          newTodos[id]: {
              ...state.todos[id],
              completed: true
            }
          })
        })
      return {
        ...state,
        todos: {
             ...state.todos,
             ...newTodos
        }
      };
    default:
      return state;
  }
};

I think this is the solution for your problem:

const todos = (state = initialState, action) => {
  switch (action.type) {
    case ActionTypes.FETCH_COMPLETED_SUCCESS:
      return {
        ...state,
        todos: Object.keys(action.payload.completedIds).reduce((previous, current) => {
            previous[current] = {
              ...state.todos[current],
              completed: true
            }
            return previous;
          }, {})
        })
      };
    default:
      return state;
  }
};

I used this solution because returns a new object and leaves the original object as it is

You can try this:

 const todos = (state = initialState, action) => {
  switch (action.type) {
    case ActionTypes.FETCH_COMPLETED_SUCCESS:
      const newState = {};//New Object
      const filterKeys = Object.keys(state).filter( key=>{
        newState[key]=state[key]; //copy
        return completedIds.indexOf(key)!=-1;
      });
      filterKeys.forEach(key=> newState[key].completed = true);
       return newState;
    default:
      return state;
  }
};
Related