Why cant state be mutated.... #758

Closed
justjacksonn opened this Issue Sep 20, 2015 · 13 comments

Comments

7 participants
@justjacksonn

Hi,

I read with interest about immutable, and how you should not mutate your state. I understand to some degree why that is, yet, the "concerned about the time spent creating new objects" side of me is either not understanding in JS how fast/slow this is, or I am missing something else.

Typically, creating a new object, more or less cloning an old object to the new plus the new state additions (or subtractions) seems to me like it would be more time consuming then just modifying the existing state. So I am trying to grasp why the immutable thing is so important and does it affect something else down the chain that I am just not seeing?

Furthermore.. I would think that a lot of what Reduct offers could be added to the react-core, and maybe even take advantage of its DOM diffing engine... maybe even to help determine if state should mutate or not. I am not sure if the process of mutating state would be faster or slower than diffing the state with the new state and change (mutate) only the parts of the actual state object that should change?

I am relatively new to all this so forgive me if I am missing something around the notion of not mutating state. I do love the idea that components should only render immutable data... but then again, to the component, it doesnt care or know if its immutable or not.. its just data that gets passed in from somewhere, so I am struggling a bit with the whole immutable vs mutable state.

@evgenyrodionov

This comment has been minimized.

Show comment
Hide comment

@timdorr timdorr added the question label Sep 21, 2015

@timdorr

This comment has been minimized.

Show comment
Hide comment
@timdorr

timdorr Sep 21, 2015

Member

It's all about predictability and reliability.

Reducers in redux are pure functions, which means they have no side effects. As soon as you start looking at some external state to those functions, they are no longer pure. And if they're not pure, they can be unreliable. And that causes bugs, many of which can be very hard to track down.

I've found through writing pure functions, I produce less errors in my code and I am more productive. And hot module reloading (enabled by using pure functions) is a turbocharger on top of that productivity.

Member

timdorr commented Sep 21, 2015

It's all about predictability and reliability.

Reducers in redux are pure functions, which means they have no side effects. As soon as you start looking at some external state to those functions, they are no longer pure. And if they're not pure, they can be unreliable. And that causes bugs, many of which can be very hard to track down.

I've found through writing pure functions, I produce less errors in my code and I am more productive. And hot module reloading (enabled by using pure functions) is a turbocharger on top of that productivity.

@evgenyrodionov

This comment has been minimized.

Show comment
Hide comment
@evgenyrodionov

evgenyrodionov Sep 21, 2015

Contributor

And you receive for free features like time travel which is very handy.

Contributor

evgenyrodionov commented Sep 21, 2015

And you receive for free features like time travel which is very handy.

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Sep 21, 2015

Collaborator

Closing as duplicate of #328.

Collaborator

gaearon commented Sep 21, 2015

Closing as duplicate of #328.

@gaearon gaearon closed this Sep 21, 2015

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Sep 21, 2015

Collaborator

Just to clarify: state is not deeply cloned on every action. Only the parts that changed are cloned (again, not deeply—depends on what changed). For example, when a todo is edited in TodoMVC app, only that todo object is cloned. The rest of the todo objects are the same. Of course, a root new todo list array is created, pointing to the new object, but the objects themselves are not cloned if they have not changed. Therefore it's not as expensive as it may seem. Furthermore, when it gets expensive (e.g. fast array changes), you can start using a library like Immutable.js that has very fast copying thanks to structural sharing. With Immutable.js, copying even large arrays isn't really that expensive because large chunks of the memory are reused. Finally, whether with or without Immutable.js, immutability helps us efficiently rerender the app because we know what exactly has changed thanks to the objects not being mutated.

Collaborator

gaearon commented Sep 21, 2015

Just to clarify: state is not deeply cloned on every action. Only the parts that changed are cloned (again, not deeply—depends on what changed). For example, when a todo is edited in TodoMVC app, only that todo object is cloned. The rest of the todo objects are the same. Of course, a root new todo list array is created, pointing to the new object, but the objects themselves are not cloned if they have not changed. Therefore it's not as expensive as it may seem. Furthermore, when it gets expensive (e.g. fast array changes), you can start using a library like Immutable.js that has very fast copying thanks to structural sharing. With Immutable.js, copying even large arrays isn't really that expensive because large chunks of the memory are reused. Finally, whether with or without Immutable.js, immutability helps us efficiently rerender the app because we know what exactly has changed thanks to the objects not being mutated.

@justjacksonn

This comment has been minimized.

Show comment
Hide comment
@justjacksonn

justjacksonn Sep 21, 2015

Ahh.... see that last bit makes sense now... I assume by this you are saying that because of the component render change due to only what state changed, the react diff/redraw engine is faster as a result? Similar I guess to shouldComponentUpdate

Ahh.... see that last bit makes sense now... I assume by this you are saying that because of the component render change due to only what state changed, the react diff/redraw engine is faster as a result? Similar I guess to shouldComponentUpdate

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Sep 21, 2015

Collaborator

I assume by this you are saying that because of the component render change due to only what state changed, the react diff/redraw engine is faster as a result? Similar I guess to shouldComponentUpdate

Exactly, react-redux uses an aggressive shouldComponentUpdate under the hood thanks to the immutability guarantee.

Collaborator

gaearon commented Sep 21, 2015

I assume by this you are saying that because of the component render change due to only what state changed, the react diff/redraw engine is faster as a result? Similar I guess to shouldComponentUpdate

Exactly, react-redux uses an aggressive shouldComponentUpdate under the hood thanks to the immutability guarantee.

@hampsterx hampsterx referenced this issue in elgerlambert/redux-localstorage Mar 24, 2016

Open

Performance overhead for non-change actions #29

@jrob8577 jrob8577 referenced this issue in ian-deans/react-tetris Jan 20, 2017

Open

Tetris dueling #2

@dougajmcdonald

This comment has been minimized.

Show comment
Hide comment
@dougajmcdonald

dougajmcdonald Dec 5, 2017

@gaearon Sorry to necro this one a little! but your comment about fast array updates:

Furthermore, when it gets expensive (e.g. fast array changes), you can start using a library like Immutable.js that has very fast copying thanks to structural sharing.

I'm looking at using redux for handling the updates to an array going up to 8000 positions and expect the throughput to be in the region of 10-100 changes per 100ms. Am I fighting a losing battle to try to manage the state that way using redux?
We're planning to represent the state through a canvas rather than the DOM, but I'd like to understand whether in your experience the frequency of updates would cause us a problem or not.

@gaearon Sorry to necro this one a little! but your comment about fast array updates:

Furthermore, when it gets expensive (e.g. fast array changes), you can start using a library like Immutable.js that has very fast copying thanks to structural sharing.

I'm looking at using redux for handling the updates to an array going up to 8000 positions and expect the throughput to be in the region of 10-100 changes per 100ms. Am I fighting a losing battle to try to manage the state that way using redux?
We're planning to represent the state through a canvas rather than the DOM, but I'd like to understand whether in your experience the frequency of updates would cause us a problem or not.

@markerikson

This comment has been minimized.

Show comment
Hide comment
@markerikson

markerikson Dec 5, 2017

Contributor

@dougajmcdonald : My first question is, can any of that work be batched up in some way? Do you really need to represent each individual update as a separate redraw of the UI?

Beyond that, I'd suggest that you look at these resources on Redux-related performance:

I'd also be happy to chat about this more over in the Reactiflux chat channels.

Contributor

markerikson commented Dec 5, 2017

@dougajmcdonald : My first question is, can any of that work be batched up in some way? Do you really need to represent each individual update as a separate redraw of the UI?

Beyond that, I'd suggest that you look at these resources on Redux-related performance:

I'd also be happy to chat about this more over in the Reactiflux chat channels.

@dougajmcdonald

This comment has been minimized.

Show comment
Hide comment
@dougajmcdonald

dougajmcdonald Dec 5, 2017

@markerikson Unfortunately yes, the concept of the game involves many fast updates to a relatively large data structure.
I realise I wasn't totally clear in my original comment, when I say 10-100 changes, these would not all trigger redraws. The 10-100 changes are the state changes which would be represented by a single redraw per 100ms.
So my question is really, if I were to batch up my updates into 10-100 state changes per 100ms, would the state management within redux be able to effectively process these in a way which would be sensibly completed within a 100ms'esque window to allow a redraw without things starting to lag behind.
The state updates would basically involve changing 3-4 properties in an array of size 8000, so we'd be talking about creating a new array 1-10 times per ms (assuming we want to keep things immutable) with some changes to one of the objects within the array index. The objects are quite small (3-4 properties, a couple of numbers and a couple of small strings)

This is why I wondered about the benefits of using immutable as Dan suggested as if we can re-use as much of the array as possible this will likely improve the performance.

@markerikson Unfortunately yes, the concept of the game involves many fast updates to a relatively large data structure.
I realise I wasn't totally clear in my original comment, when I say 10-100 changes, these would not all trigger redraws. The 10-100 changes are the state changes which would be represented by a single redraw per 100ms.
So my question is really, if I were to batch up my updates into 10-100 state changes per 100ms, would the state management within redux be able to effectively process these in a way which would be sensibly completed within a 100ms'esque window to allow a redraw without things starting to lag behind.
The state updates would basically involve changing 3-4 properties in an array of size 8000, so we'd be talking about creating a new array 1-10 times per ms (assuming we want to keep things immutable) with some changes to one of the objects within the array index. The objects are quite small (3-4 properties, a couple of numbers and a couple of small strings)

This is why I wondered about the benefits of using immutable as Dan suggested as if we can re-use as much of the array as possible this will likely improve the performance.

@markerikson

This comment has been minimized.

Show comment
Hide comment
@markerikson

markerikson Dec 5, 2017

Contributor

@dougajmcdonald : well, "the state management in Redux" is really just the one root reducer function you've provided :)

Immutable.js is not a magic perf-improving silver-bullet. It can make some things faster, and other things slower. I've got multiple articles on Immutable.js-related perf considerations you might want to look at.

To be honest, given your use case, you could in theory get away with direct mutation more if you wanted to. Per my post Idiomatic Redux, Part 1 - Implementation and Intent, the Redux core itself doesn't actually care about mutation at all - it's primarily the React-Redux UI layer that does, as well as the DevTools. Now, mutation isn't how you're intended to use Redux, but it's possible.

My personal advice would be to start simple. Just use plain JS objects and arrays. Update them immutably, either "by hand" or using one of the many immutable update utility libraries available. See how well that performs for you.

Then, from there, you can do additional optimization work in terms of batching, cutting down on dispatches, update logic, and so on.

Contributor

markerikson commented Dec 5, 2017

@dougajmcdonald : well, "the state management in Redux" is really just the one root reducer function you've provided :)

Immutable.js is not a magic perf-improving silver-bullet. It can make some things faster, and other things slower. I've got multiple articles on Immutable.js-related perf considerations you might want to look at.

To be honest, given your use case, you could in theory get away with direct mutation more if you wanted to. Per my post Idiomatic Redux, Part 1 - Implementation and Intent, the Redux core itself doesn't actually care about mutation at all - it's primarily the React-Redux UI layer that does, as well as the DevTools. Now, mutation isn't how you're intended to use Redux, but it's possible.

My personal advice would be to start simple. Just use plain JS objects and arrays. Update them immutably, either "by hand" or using one of the many immutable update utility libraries available. See how well that performs for you.

Then, from there, you can do additional optimization work in terms of batching, cutting down on dispatches, update logic, and so on.

@dougajmcdonald

This comment has been minimized.

Show comment
Hide comment
@dougajmcdonald

dougajmcdonald Dec 6, 2017

@markerikson subtle jibe taken on the nose! :p Yeah you're right the reducer side of things at the moment is basically the creation and destruction of a fairly large array but I think the real issue is more likely the react component lifecycle bits and bobs when the end goal is going to be a canvas element ideally rendering at 60ish FPS.
Thanks for the point on immutable and the points on direct mutation I will read up a bit and consider these too.
At the moment my thought process is to disconnect the redux state from the canvas and just pass messages to it via pub/sub (which is how we have it hooked up already) just with more moving parts

I'm thinking of changing from:
pubsub > dispatch() > reducer > redux state > react component stuff > canvas render.

To:
pubsub > local component state > canvas render

I will still use redux for the rest of the UI, just perhaps not the canvas part of the app.

@markerikson subtle jibe taken on the nose! :p Yeah you're right the reducer side of things at the moment is basically the creation and destruction of a fairly large array but I think the real issue is more likely the react component lifecycle bits and bobs when the end goal is going to be a canvas element ideally rendering at 60ish FPS.
Thanks for the point on immutable and the points on direct mutation I will read up a bit and consider these too.
At the moment my thought process is to disconnect the redux state from the canvas and just pass messages to it via pub/sub (which is how we have it hooked up already) just with more moving parts

I'm thinking of changing from:
pubsub > dispatch() > reducer > redux state > react component stuff > canvas render.

To:
pubsub > local component state > canvas render

I will still use redux for the rest of the UI, just perhaps not the canvas part of the app.

@jscontreras

This comment has been minimized.

Show comment
Hide comment
@jscontreras

jscontreras Apr 24, 2018

Your questions are totally valid. Actually the vuejs implementation of redux called vuex works under the concept of mutation. So the 'reducers' are in fact called mutations and as long as you modify the state in a centralized place everything should be fine. This happens because vue adds observers and other stuff to the state of the app so as you mentioned it makes more sense in the cae of vue to modify rather than replace the object. Redux is inmutable and at the end the performance of both approaches react VS vue virtual DOM updates is mostly the same at the point that is not possible to say that for all cases one is better than the other

Your questions are totally valid. Actually the vuejs implementation of redux called vuex works under the concept of mutation. So the 'reducers' are in fact called mutations and as long as you modify the state in a centralized place everything should be fine. This happens because vue adds observers and other stuff to the state of the app so as you mentioned it makes more sense in the cae of vue to modify rather than replace the object. Redux is inmutable and at the end the performance of both approaches react VS vue virtual DOM updates is mostly the same at the point that is not possible to say that for all cases one is better than the other

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