Search code examples
javascriptreactjsredux

Redux returning error message when returning new value from action creator


I am currently working on a trial app where I allow users to vote on the best anecdote and when a user votes, the action creator should return a new sorted array of objects. However, i keep getting this error message:

An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.

I am kind of confused with this error message because I do not see where I have modified the original state because I would assume filter and map would not mess with the state. Where am i wrong? Here is my code:

import _, { sortBy } from 'underscore';
import { createSlice, current } from '@reduxjs/toolkit';

const anecdotesAtStart = [
  'If it hurts, do it more often',
  'Adding manpower to a late software project makes it later!',
  'The first 90 percent of the code accounts for the first 90 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.',
  'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.',
  'Premature optimization is the root of all evil.',
  'Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.'
]

const getId = () => (100000 * Math.random()).toFixed(0)

const asObject = (anecdote) => {
  return {
    content: anecdote,
    id: getId(),
    votes: 0
  }
}

const initialState = anecdotesAtStart.map(asObject)

const anecdoteSlice = createSlice({
  name: 'anecdote',
  initialState,
  reducers: {
    createNotification(state, action) {
      return action.payload;
    },
    increaseVote(state, action) {
      const currentArray = state.filter(obj => obj.id === action.payload)
      const newState = state.map(obj => {
      // 👇️ if id equals 2 replace object
      if (obj.id === currentArray[0].id) {
        currentArray[0].votes += 1
        return currentArray[0]
      }
      // 👇️ otherwise return object as is
      return obj;
    });
      const sortedArray = _.sortBy(newState, 'votes')
      return sortedArray
    },
    createAnecdote(state, action) {
      const newAnecdote = action.payload
      const initializedAnecdote = asObject(newAnecdote)
      const updatedAnecdotes = state.concat(initializedAnecdote)
      return updatedAnecdotes;
    },

  }})

export const { createNotification, increaseVote, createAnecdote} = anecdoteSlice.actions
export default anecdoteSlice.reducer

I believe the error is occurring in increaseVote:

increaseVote(state, action) {
      const currentArray = state.filter(obj => obj.id === action.payload)
      const newState = state.map(obj => {
      // 👇️ if id equals 2 replace object
      if (obj.id === currentArray[0].id) {
        currentArray[0].votes += 1
        return currentArray[0]
      }
      // 👇️ otherwise return object as is
      return obj;
    });
      const sortedArray = _.sortBy(newState, 'votes')
      return sortedArray
    },

Solution

  • I am confused with this error message because I do not see where I have modified the original state because I would assume the filter and map would not mess with the state. Where am I wrong?

    Answer to this question

    Since your state is an array of objects when you run this line of code, you think that the filter() is returning a new array, you are right but the items which are of type reference get copied into the new array

    So every item inside the currentArray will point to items inside the original array and if you are modifying any item from currentArray, the changes will be reflected in the original array as well. This concept is known as shallow copies

     const currentArray = state.filter(obj => obj.id === action.payload)
    

    Shallow Copy ->

    A shallow copy of an object is a copy whose properties share the same references (point to the same underlying values) as those of the source object from which the copy was made. As a result, when you change either the source or the copy, you may also cause the other object to change too — and so, you may end up unintentionally causing changes to the source or copy that you don't expect. That behaviour contrasts with the behaviour of a deep copy, in which the source and copy are completely independent.

    For example, if in a shallow copy named copy of an array object, the value of the copy[0] element is {"list":["butter","flour"]}, and you do copy[0].list = ["oil","flour"], then the corresponding element in the source object will change, too — because you selectively changed a property of an object shared by both the source object and the shallow copy.

    However, if instead you do copy[0] = {"list":["oil","flour"]}, then the corresponding element in the source object will not change — because in that case, you're not just selectively changing a property of an existing array element that the shallow copy shares with the source object; instead you're actually assigning a completely new value to that copy[0] array element, just in the shallow copy.

    In JavaScript, all standard built-in object-copy operations (spread syntax, Array.prototype.concat(), Array.prototype.slice(), Array.from(), Object.assign(), and Object.create()) create shallow copies rather than deep copies.

    reference -> https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy