Sydney
Sydney

Reputation: 1429

Redux initial state gets mutated even when using Object.assign

This is a simple replication of a problem i encounter in an actual app. https://jsfiddle.net/zqb7mf61/

Basically, if you clicked on 'Update Todo" button, the text will change from "Clean Room" to "Get Milk". "Clean Room" is a value in the initial State of the reducer. Then in my React Component, I actually try to clone the state and mutate the clone to change the value to "Get Milk" (Line 35/36). Surprisingly, the initial State itself is also mutated even though I try not to mutate it (as seen in line 13 too).

I am wondering why Object.assign does not work for redux.

Here are the codes from the jsFiddle.

REDUX

const initState = {
    task: {id: 1, text: 'Clean Room'}
}

// REDUCER
function todoReducer (state = initState, action) {
  switch (action.type) {
   case 'UPDATE_TODO':
        console.log(state)
        let newTodo = Object.assign({}, state)  // here i'm trying to not make any changes. But i am surpise that state is already mutated.
      return newTodo
    default:
        return state;
  }
}

// ACTION CREATORS:
function updateTodo () {
    return {type: 'UPDATE_TODO'};
}


// Create Store
var todoStore = Redux.createStore(todoReducer);

REACT COMPONENT

//REACT COMPONENT
 class App extends React.Component{
    _onSubmit = (e)=> {
    e.preventDefault();
    let newTodos = Object.assign({}, this.props.todos)  // here i clone the redux state so that it will not be mutated, but i am surprise that it is mutated and affected the reducer.
    newTodos.task.text = 'Get Milk'
    console.log(this.props.todos)
    this.props.updateTodo();
  }

    render(){
    return (
        <div>
        <h3>Todo List:</h3>
        <p> {this.props.todos.task.text} </p>

        <form onSubmit={this._onSubmit} ref='form'>
          <input type='submit' value='Update Todo' />
        </form>      

      </div>
     );
  }
 }

// Map state and dispatch to props
function mapStateToProps (state) {
    return {
    todos: state
    };
}

function mapDispatchToProps (dispatch) {
    return Redux.bindActionCreators({
    updateTodo: updateTodo
  }, dispatch);
 }
  // CONNECT TO REDUX STORE
 var AppContainer = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(App);

Upvotes: 3

Views: 1124

Answers (1)

Mikroware
Mikroware

Reputation: 344

You use Object.assign in both the reducer as in the component. This function only copies the first level of variables within the object. You will get a new main object, but the references to the objects on the 2nd depth are still the same.

E.g. you just copy the reference to the task object around instead of actually creating a new task object.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Deep_Clone


Apart from that it would be better to not load the whole state into your component and handle actions differently. Lets just solve this for now. You will have to create a new task object in your onSubmit instead of assigning a new text to the object reference. This would look like this:

newTodos.task = Object.assign({}, newTodos.task, {text: 'Get Milk'})

Furthermore to actually update the store, you will have to edit your reducer as you now assign the current state to the new state. This new line would look like this:

let newTodo = Object.assign({}, action.todos)

Upvotes: 5

Related Questions