23

I have built my first React application with stateful stores the "normal" way, and now I am looking into using an immutable global state like used in the Este starterkit.

  • The state of all stores is kept together in a single immutable data structure
  • Components have no state but access data in their render() based on a store getter function
  • Stores are also stateless but mutate the global application state for their domain using a cursor.
  • The top level app component listens for state changes, and re-renders the whole component tree.
  • Components are implemented as "pure" meaning they use shouldComponentUpdate to efficiently figure out of they can be skipped in the re-rendering.

It simplifies the application structure in a few ways:

  • Components don't listen to stores and also don't copy store data to their local state. They simply grab their store state on each render.
  • The global state is always a snapshot of the whole application, making it easier to debug and adding features like undo trivial.
  • The global state seems to simplify isomorphic rendering.

I only read positive things about using immutable data with React, and it is recommended to avoid state in components, so I wonder if there are any disadvantages. I figured there must be because otherwise I don't see why it isn't recommended as the way to structure React apps.

Immutability is new to me, so are there any caveats I should be aware of if I start using this approach in a complex real world app?

The only minor thing I can think of is the use of forceUpdate() as Este is using it, as I've read that it is a synchronous function. Morearty for example seems to defer the updates to the next animation frame in order to batch them, but I consider this an implementation detail / optimization and not some inherit downside of the immutable single state approach.

Thijs Koerselman
  • 21,680
  • 22
  • 74
  • 108
  • Probably more object creation (because data manipulation functions return copies), and therefore higher memory usage and GC churn. – joews Apr 14 '15 at 08:44
  • @joews not necessarily. Projects that use persistent data structures, like [mori](http://swannodette.github.io/mori/), do not incur cost in that way. – Davin Tryon Apr 14 '15 at 09:42
  • @DavinTryon I agree, hence the "probably" - it'll happen unless you take steps to avoid it. – joews Apr 14 '15 at 09:57
  • 1
    Maybe the memory consuming because you will work on copy of your structure and not on the structure himself. But there is advantages like to parallelize easily your code with immutable. – alifirat Apr 14 '15 at 11:18
  • I think the reason React does not clearly say we should use this pattern is more philosophical than technical (performance, codebase size and maintainability, scalability, etc.). React is just one component of the whole picture and it's maintainer (Facebook) is probably trying to distance itself from all the other components (not implement them themselves) because (1) there already are great implementations for them (mori, react-router, flux?) and (2) it means less work making sure everything fits together perfectly for them. – Sergiu Paraschiv Apr 14 '15 at 11:23
  • It appears to me that this memory consumption issue is mostly speculation. Have you ever heard of a project where this became anywhere close to a real problem? – Thijs Koerselman Apr 14 '15 at 17:25
  • @SergiuParaschiv that makes sense. Also I don't know if this approach fits well with their upcoming Relay architecture. Still if there are no real issues with this approach it would be nice to hear from people that have used it on substantial projects. – Thijs Koerselman Apr 14 '15 at 17:28
  • @afk What do you mean by parallelize in the context of Javascript? – Thijs Koerselman Apr 15 '15 at 06:22
  • I think the reason Facebook didn't make immutable data the default is that the current way is very approachable for developers coming from the object oriented world. Immutable data makes sense for a real world application, but it does complicate simple examples quite a bit. – OlliM Apr 15 '15 at 13:18
  • Immutable uses structural sharing to keep memory usage in check. Its one area where it outperforms an equivalent mutable structure. – hazardous Apr 01 '16 at 13:13

3 Answers3

15
  1. Documentation. Some of the most voted questions/rants in SO are about trivial things like updating nested list items all because the documentation is more oriented towards an audience familiar with functional programming, which makes it less approachable to the rest.
  2. Its not trivial to separate the model hierarchy knowledge from your components. I hate to do a this.state.getIn("[parent, child, index]") in a component because it increases the possibility of a model change breaking the component code. This is preventable by extensibility (more on that below) or through helper methods, but you certainly lose the simplicity of plain JS members.
  3. A significant challenge I faced was to be able to serialize and de-serialize the state. The fromJS method supports custom revivers but they are a pita and require careful testing and maintenance.
  4. The Record type is severely stunted by a debilitation called nesting. This is sad because it allows easier (in relative terms) extensibility, but encourages only a single level hierarchy. You cannot easily create nested records from JSON. This makes it difficult to mix them with regular immutable fromJS usage unless you use the above mentioned pita revivers. And did I mention how sad that is, given Records expose model properties as first class members, ensure model integrity and defaults.
  5. And then there is extensibility. Try adding helpers around your immutable data models to abstract model hierarchy dependency in your components and you are up for a noticeable hurdle race. De-serializing becomes a bitter divorce battle. You would need to mess around with revivers, if Records are in the mix they will cry foul and so on. An easier extensibility mechanism would go a long way towards making my components code cleaner.
  6. No documented best practices. Although this sits right with #1, I still should mention that the lack of good documentation prevents one to pick the best possible way of doing something. I am not sure if I should pick from using an updater, or forEach or setIn (and so on) to update an immutable structure. The methods don't cross-reference each other enough to let you know what alternatives are around.
hazardous
  • 10,627
  • 2
  • 40
  • 52
  • 1
    Thanks. I'll accept this as the answer since it gives more insights besides the obvious. – Thijs Koerselman Apr 03 '16 at 11:47
  • 3
    Most of these arguments seem to be arguments against using Immutable.js. You can write normal Javascript and still take care not to mutate any objects, thus obtaining practical immutability. – efdee Jan 11 '17 at 15:22
  • 1
    You are right, however the question has been tagged with ImmutableJS and the discussion has mostly been focused around it. – hazardous Jan 12 '17 at 05:53
6

One advantage of passing data through props is that you can utilize shouldComponentUpdate for more performant updates. If you assume data always comes from the parent (e.g. via props), you can effectively prevent a large portion of your component tree from having to rerender. Immutable values make this very nice as you can do reference equality checks in shouldComponentUpdate relatively high in your component hierarchy.

The only other disadvantage I've found when using immutable data with React is that the React developer tools don't work super well with them (they show you the immutable values' underlying data structure instead of a JS-friendly value).

Michelle Tilley
  • 157,729
  • 40
  • 374
  • 311
  • The advantages I know about, that's why I want to use it :) Is inspecting the immutable data also an issue for components further down the hierarchy? I mean, if the data is passed on as props won't it show up in more readable form there? – Thijs Koerselman Apr 14 '15 at 17:22
  • @0x80 Yes, if you convert immutable values to plain JS values and pass them down props, they'll be more readable, but you'd lose the ease of reference equality in `shouldComponentUpdate` further down the tree. May be a good trade off. – Michelle Tilley Apr 14 '15 at 17:27
  • 1
    Thanks. Let's hope someone finds a solution to display the values nicely in the React dev tools some day. – Thijs Koerselman Apr 14 '15 at 17:42
3

I know the answer has already been accepted, but I find the primary disadvantage to be that you pass non-plain JavaScript objects into your component code, meaning you cannot use regular JavaScript property accessors when reading values from the Immutable objects passed in via props. Instead you have to use Immutable.js' read API. This is a significant trade-off. You cannot keep your use of Immutable.js contained where it belongs, in the store; it has now leaked itself throughout your entire application. This is a pain you may feel when Immutable.js is replaced by the next buzzy Immutability library that has it's own API or is able to use the native property accessors. Additionally, third party code will almost certainly expect plain old JavaScript Objects, so Immutable objects will need to be converted before passing them along. If you plan on introducing Immutable.js into an existing application, it will become unclear very quickly in your component code which props are Immutable and which are JS object, unless you are very disciplined and come up with a naming scheme or some other method that you are consistent with as you pass objects down the rendering chain.

sethro
  • 2,127
  • 1
  • 14
  • 30
  • Have a look at Records. They allow some similarity with a more traditional JS object in terms of exposing first class members and extensibility. But they have their own insecurities, see my response below. – hazardous Apr 01 '16 at 13:15