Redux

Why is immutability important in redux?

Spread the love
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Immutability myths

There are a lot of myths and misconceptions about why our application state needs to be immutable when we use redux.
Often I hear things like:

  • Using an immutable object is faster because you avoid creating new objects.
  • Immutable objects are always faster than mutable ones.
  • Mutating objects is a bad practice.

First of all, mutating objects is entirely fine from a programming point view, both mutability and immutability have their own applications in different contexts. The thing is that Redux needs your objects to be immutable because of the way that it works

The main reasons why Redux needs immutable objects are PERFORMANCE AND PREDICTABILITY.

PERFORMANCE

Redux needs to be able to detect when its store changes and one of the main problems about detecting changes in a mutable object is that unless you traverse the whole object(or use some pretty hard and sophisticated algorithm), there is no way to tell for sure if the object changed or not. The larger and more complex your object is the more prominent your performance problems will be.

One of the keys to the performance behind redux is the way it detects changes. Instead of making a deep comparison it makes a shallow comparison between the objects.

In other words, Redux just does something like this

oldObject === newObject

Differences between deep compare and shallow compare.

Basically, when you deep compare two objects, you need to iterate over each of its fields and compare the values.

It’s easy to see that the bigger you object is, the slower your comparison will be.

In another hand when you shallow compare two objects your comparison algorithm will always run in constant time O(1) because you just make a simple comparison like this

a === b

 

This means that doesn’t matter how large your object gets the comparison will always be blazing fast. The immutability has a crucial role here because if you mutate your object and return it like in the below example.

function incrementFoo(myObj){
   myObj.foo++
   return myObj
}

let a = {foo:1}
let b = incrementFoo(a)

//this will be true as both variables contain
//a reference to the same object
a === b

As both objects are the same redux will not be able to detect changes. But when you have immutable objects, you are forced to make a copy of the original object, mutate the copy and then return it, so the two objects will point to different references what will make change detection very quick and cheap from a performance point of view.

Predictability

Another reason why redux needs immutable objects is because the usage of immutability and pure functions brings a high degree of predictability to the code. So it doesn’t matter what happens if you call a pure function that deals with immutable objects 1000 times with the same parameters, you will get 1000 times the same result, irrespective of what happened before in your code.

 

Pure functions have these three characteristics:

  1. Given the same input, you will always receive the same output.
  2. Produces no side effects.
  3. Does not depends on external factors.

Pure function vs. impure function

Take this function as an example, if you run it 1000 times with the same parameters you will always get the same output

//pure function
function calculateBonus(salary){
   let bonusPercentage = 0.2
   return salary*bonusPercentage
}

The same is not true for the following one:

let bonusPercentage = 0.2

//impure function
function calculateBonus(salary){
   return salary*bonusPercentage
}

Both functions might look the same to you, but the problem with the second one is that it relies on an external variable. So you can’t take for granted that the value of bonusPercentage will not change. This might potentially cause the function to return different values even if it receives the same input values.

Here comes another example of an impure function:

const employee = {name:'John Doe', salary:75000}

//another impure function
//it produces side effects
function calculateEmployeeRaise(employee){
   let raisePercentage = 0.2
   employee.salary *= raisePercentage
   return employee
}

This function is an impure function because it mutates the object state so it’s generating side effects. The immutable way to do this would be:


const employee = {name:'John Doe', salary:75000}

//another impure function
//it produces side effects
function calculateEmployeeRaise(employee){
   let raisePercentage = 0.2
   //here we create a copy of the original object
   //so we will not mutate the original one

   employee = Object.assign({},employee)
   employee.salary *= raisePercentage
   return employee
}

 Avoid rendering components unnecessarily.

Some words about how react-redux chooses when to re-render a component, when react redux detect that your store state changed it will pass the state down and invoke the mapStateToProps from your connected components, then it will iterate over the returned object’s keys and perform a shallow compare with the previous state. If it detects any change on these keys it will trigger a render, this means that if a particular field of your state didn’t change, but you are transforming the state inside your mapStateToProps, you might be unnecessarily triggering a new render.

An example of this problem is using a map or filter function inside your mapStateToProps because these functions always return a new reference, so from the react-redux point of view you object changed (even if its values remain the same)

// mapStateToProps
const mapStateToProps = state => ({
  //admins will be a different object
  //every time the mapStateToProps is invoked
  //because even if we have the same values 
  //inside the returned array
  //the array itself will be a new array
  ///this will for a new render
  admins : state.users.filter(user => user.role === ' admin')
})

To avoid such problems, you can use some memoization lib like reselect so you will make sure that you will only render components when something really changed ensuring that you are getting the best performance you can from react.