This article is the second part of the “The React.js Way” blog series. If you are not familiar with the basics, I strongly recommend you to read the first article: The React.js Way: Getting Started Tutorial.
In the previous article, we discussed the concept of the virtual DOM and how to think in the component way. Now it’s time to combine them into an application and figure out how these components should communicate with each other.
Components as functions
The really cool thing in a single component is that you can think about it like a function
in JavaScript. When you call a function
with parameters, it returns a value. Something similar happens with a React.js component: you pass properties, and it returns with the rendered DOM. If you pass different data, you will get different responses. This makes them extremely reusable and handy to combine them into an application.
This idea comes from functional programming that is not in the scope of this article. If you are interested, I highly recommend reading Mikael Brevik’s Functional UI and Components as Higher Order Functions blog post to have a deeper understanding on the topic.
Top-down rendering
Ok it’s cool, we can combine our components easily to form an app, but it doesn’t make any sense without data. We discussed last time that with React.js your app’s structure is a hierarchy that has a root node where you can pass the data as a parameter, and see how your app responds to it through the components. You pass the data at the top, and it goes down from component to component: this is called top-down rendering.
It’s great that we pass the data at the top, and it goes down via component’s properties, but how can we notify the component at a higher level in the hierarchy if something should change? For example, when the user pressed a button?
We need something that stores the actual state of our application, something that we can notify if the state should change. The new state should be passed to the root node, and the top-down rendering should be kicked in again to generate (re-render) the new output (DOM) of our application. This is where Flux comes into the picture.
Flux architecture
You may have already heard about Flux architecture and the concept of it.
I’m not going to give a very detailed overview about Flux in this article; I’ve already done it earlier in the Flux inspired libraries with React post.
Application architecture for building user interfaces – Facebook flux
A quick reminder: Flux is a unidirectional data flow concept where you have a Store
which contains the actual state of your application as pure data. It can emit events when it’s changed and let your application’s components know what should be re-rendered. It also has a Dispatcher
which is a centralized hub and creates a bridge between your app and the Store
. It has actions that you can call from your app, and it emits events for the Store
. The Store
is subscribed for those events and change its internal state when it’s necessary. Easy, right? 😉
PureRenderMixin
Where are we with our current application? We have a data store that contains the actual state. We can communicate with this store and pass data to our app that responds for the incoming state with the rendered DOM. It’s really cool, but sounds like lot’s of rendering: (it is). Remember component hierarchy and top-down rendering – everything responds to the new data.
I mentioned earlier that virtual DOM optimizes the DOM manipulations nicely, but it doesn’t mean that we shouldn’t help it and minimize its workload. For this, we have to tell the component that it should be re-rendered for the incoming properties or not, based on the new and the current properties. In the React.js lifecycle you can do this with the shouldComponentUpdate
.
React.js luckily has a mixin called PureRenderMixin which compares the new incoming properties with the previous one and stops rendering when it’s the same. It uses the shouldComponentUpdate
method internally.
That’s nice, but PureRenderMixin
can’t compare objects properly. It checks reference equality (===
) which will be false
for different objects with the same data:
boolean shouldComponentUpdate(object nextProps, object nextState)
If
shouldComponentUpdate
returns false, thenrender()
will be skipped until the next state change. (In addition,componentWillUpdate
andcomponentDidUpdate
will not be called.)
var a = { foo: 'bar' };
var b = { foo: 'bar' };
a === b; // false
The problem here is that the components will be re-rendered for the same data if we pass it as a new object (because of the different object reference). But it also not gonna fly if we change the original Object because:
var a = { foo: 'bar' };
var b = a;
b.foo = 'baz';
a === b; // true
Sure it won’t be hard to write a mixin that does deep object comparisons instead of reference checking, but React.js calls shouldComponentUpdate
frequently and deep checking is expensive: you should avoid it.
I recommend to check out the advanced Performance with React.js article by Facebook.
Immutability
The problem starts escalating quickly if our application state is a single, big, nested object like our Flux store.
We would like to keep the object reference the same when it doesn’t change and have a new object when it is. This is exactly what Immutable.js does.
Immutable data cannot be changed once created, leading to much simpler application development, no defensive copying, and enabling advanced memoization and change detection techniques with simple logic.
Check the following code snippet:
var stateV1 = Immutable.fromJS({
users: [
{ name: 'Foo' },
{ name: 'Bar' }
]
});
var stateV2 = stateV1.updateIn(['users', 1], function () {
return Immutable.fromJS({
name: 'Barbar'
});
});
stateV1 === stateV2; // false
stateV1.getIn(['users', 0]) === stateV2.getIn(['users', 0]); // true
stateV1.getIn(['users', 1]) === stateV2.getIn(['users', 1]); // false
As you can see we can use ===
to compare our objects by reference, which means that we have a super fast way for object comparison, and it’s compatible with React’s PureRenderMixin
. According to this we should write our entire application with Immutable.js. Our Flux Store should be an immutable object, and we pass immutable data as properties to our applications.
Now let’s go back to the previous code snippet for a second and imagine that our application component hierarchy looks like this:
You can see that only the red ones will be re-rendered after the change of the state because the others have the same reference as before. It means the root component and one of the users will be re-rendered.
With immutability, we optimized the rendering path and supercharged our app. With virtual DOM, it makes the “React.js way” to a blazing fast application architecture.
Learn more about how persistent immutable data structures work and watch the Immutable Data and React talk from the React.js Conf 2015.
Check out the example repository with a ES6, flux architecture, and immutable.js:
https://github.com/RisingStack/react-way-immutable-flux