Skip to content

Instantly share code, notes, and snippets.

@browniefed
Created September 26, 2018 03:44
Show Gist options
  • Save browniefed/188586c1e6a2be58d0669c8fbf45ef09 to your computer and use it in GitHub Desktop.
Save browniefed/188586c1e6a2be58d0669c8fbf45ef09 to your computer and use it in GitHub Desktop.

Intro

Immer is an incredible new library for JavaScript immutability. In previously libraries like Immutable.js it required whole new methods of operating on your data.

This was great but required complicated adapaters and converting back and forth between JSON and Immutable in order to work with other libraries if needed.

Immer simplifies this and you use data and JavaScript objects as your normally would. This means when you need performance and need to know when something changes you can use a triple equal strict equality check and prove that something has indeed changed or not changed.

Your shouldComponentUpdate calls are no longer require shallow or deep equals to traverse all the data and compare.

Spread Operator

In latest JavaScript many developers depend on the spread operator ... to do immutability. For example you can spread the previous object in and override specific keys, or add in new keys. This will use Object.assign under the hood and return a new object.

https://gist.github.com/b94526b7fd91dfe554261b3871a1de0a

Our newObject will now be a completely different object so any strict equality checking (prevObject === newObject ) will be false. So it is indeed creating a new object. The name will no longer just be Jason but will be Jason Brown and because we didn't do anything with the id key it stays the same.

This applies to React in that if you have a nested object on state you need to update and spread in the previous object because React will only merge state at the base level of keys.

Lets look at an example. Say we have 2 nested counters but we only want to update one and not mess with the other.

https://gist.github.com/3e7650af976cd5fa51c2d839652d6efa

Next in our componentDidMount we'll set up an interval and update our nested counter. However we want to preserve the otherCounter value so we need to use the spread operator to bring over the previous nested state.

https://gist.github.com/81f267084ca61e37c97b532510ee0204

This is an all too common scenario in React, and if your data is really nested it adds complexity when you need to spread more than one level deep.

Immer Produce Basic

Immer allows for us to still use the mutations (directly modifying a value) but without actually worrying about managing the number of spreads, or even what data was touched and needs to be immutable.

Lets setup a scenario where you are passing in a value to increase a counter by and additionally have a user object that isn't going to be touched.

Here we render our app and pass in the increment value.

https://gist.github.com/50dceb36e3be18a7387e8920b9cf9475

https://gist.github.com/9e269dd720c7dd4b3b6183a01a6e5d54

We setup our app just like before now with a user object and a nested counter.

We'll import immer and name the default import produce. As in when given the current state, it'll help us produce the next state.

https://gist.github.com/9852f7a455294a34490e969d32b10061

Next we'll create a function called counter that takes state and props so we can read the current count and then update with the next count based upon the requested increaseCount prop.

https://gist.github.com/fa36577702a428f2ea30fb15ef9655f7

The produce method of Immer takes a state as it's first argument, and a function to mutate your data for the next state as it's second argument.

https://gist.github.com/0c79dcd24c33b0cc2516204e75414b05

Now if we put it all together. We can create a counter function that takes some state and some props and calls the produce function. We then mutate the draft of what the next state should look like and the Immer produce function will create a new immutable state for us.

https://gist.github.com/a2c2a0dd4184aac3f5c2eca9c3d71392

Our updated interval function might look something like this.

https://gist.github.com/f1b6cfa01e5baf56821487aab082dafb

However we only touched the count and counter, what happened to our user object? Did that object reference get changed as well? The answer is no. Immer knows exactly what data has been touched. So if we did an strict equality check after the component has been updated we can see that the previous user object and the next user object in state are exactly the same.

https://gist.github.com/252e33ec9856e260a367612117c90398

This is huge for when performance might matter when you use shouldComponentUpdate or need an easy way to know if a row has updated for something like FlatList in React Native.

Immer Currying

Immer can make operations even easier. If it sees that you are passing in a function as the first argument instead of an object it will create a curried function for you. So rather than the a new object the produce function returns another function.

It will take the first argument when it's called and use that as the state you want to mutate, and then additionally any other arguments will also be passed along.

So rather than a function we could create a counter function and the props will be proxied along.

https://gist.github.com/66d583f49f3fc64e6442c5cafc0e62f0

Then because produce returned a function we can pass this directly into our setState which has the ability to take a function. You should use a functional setState when you are referencing your previous state, and in our case we need to reference the previous count to increase it to it's new count. It will pass in the current state and props which is exactly what we've set our counter to expect.

So our new interval will just have this.setState receiving counter which is a function.

https://gist.github.com/8afc36cd9676be94f2c7f0cebdb7cb9f

Ending

This is obviously a contrived example but has huge real world applications. Long lists of data where a single field is updated can be easily compared and only the single row updated. Large nested forms only need to update the specific parts that were touched.

You no longer have to do a shallow or deep comparison and can now do a strict equality check and know for certain whether or not your data has actually changed and if you need to re-render.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment