The key is the unidirectional dataflow
Everybody starting with React realize quiclkly about how easy is to create components and build an UI that can be used in production.
Once you get used to how components work, you begin to feel the need of handling the data that is used by them efficently. That data is often called “the state” of the app, and managing it well is the corner stone of every frontend application.
Flux is usually the concept used to manage the data with React. It contains actions to update the state, stores to hold it and a dispatcher to coordinate the changes, but the truth is that the important statement of flux is the unidirectional data flow: When the state is updated, the application is re-rendered reactively, so it is the state who rules the application.
You might be saying now “Hey! That’s not the definition of unidirectional data flow!”. Flux definition, including stores, actions and a dispatcher, usually explains the unidirectional dataflow in a more complex way that it should. Removing all the flux pieces, the unidirectional dataflow graph can be shown like this:
The main implication of this graph is that only state changes can update the UI. You will be building a data driven interface.
There are dozens of Flux frameworks out there (even I created one some time ago). Some of them don’t have a dispatcher, others don’t include stores… But everyone follows the unidirectional dataflow.
The simplest Flux with Freezer
Freezer is not a new library, but it is pretty unkown. It’s a data structure that triggers events when it is updated, and it happens to contain all the needed to add the unidirectional data flow to your app. It also have some other benefits for React:
- Its data is immutable, an efficent way of saving memory and speed up your React app.
- It is tiny, 9kb minified.
- It has no dependencies.
- It has an open event system, the perfect dispatcher replacement.
- Data is stored in plain js array and objects, so it is possible to use popular utility libraries like underscore, lodash or rambda with it.
- Using it, you can have all the app state in one place.
These are nice reasons to give it a try, but the main one is the simplicity of developing React apps with it, without dispatcher, actions nor stores.
Freezer as the state holder
The famous todo app become really simple to create using React and Freezer. To start, let’s overview how Freezer works defining the app state in a Freezer object.
The freezer object will hold the state of our app. In order to access to the state data you need to use the get method, and you will get an immutable object with all of it.
The state data hold by Freezer is immutable. It cannot be changed, it will be replaced by new one when an update is needed. To do so, state nodes come with updater methods that request a new state for our application. For example, the push method in the arrays.
Every time the state is updated, Freezer create a new immutable object for it reusing the unmodified parts of the data tree. It also triggers an update event, so you can subscribe to it in order to refresh your UI every time the data changes. This way we will have a reactive, data driven UI.
The update event is always triggered on the next tick, so Freezer won’t try to re-render your UI if some updater method is called in a middle of a render (You should never update the state in a middle of a render, but just in case).
Freezer as the orchestra director
As I said before, using Freezer there is no need of a dispatcher, stores nor actions. You can develop in a very similar but simpler way.
- Instead of stores, all the data is hold in one Freezer object that emit events on change. It is possible to listen to changes in one particular node using listeners, in case that a fine grained rendering is needed.
- Instead of a dispatcher, we can use the open event system to coordinate the state changes.
- Instead of registering actions in the dispatcher, you can create reactions to Freezer events. Flux also recommends to create action creators (more boilerplate code) because payloads are not very sematic and they are difficult to read by the developers. Freezer events accepts any number of arguments and pass them to the reactions, easy to understand.
There is no need of having a waitFor method like in Flux, if you want to call a reaction after another, just trigger a new event at the end of the first one.
That’s it. Just events, reactions and no boilerplate code at all.
Tips to use React with Freezer
Events are the key piece for reducing boilerplate code of a traditional Flux system. We used Freezer’s ones, but you can even use DOM events if you are creating a web application, making the events bubble through the React hierarchy. Using Freezer has some other advantages though.
Familiar project structure
We don’t have dispatcher or stores, so we don’t need to register actions or bind listeners to stores often, but, we have reactions that are similar to actions. It is recommended group this actions by their domain, see the todoActions file in the todo app. Don’t you find it clear?
Components without internal state
Using Freezer it is possible to have all the data outside the components. Did you realize how we had a todoInput property in our state? There the value of the main input to create new todos is stored. Everytime the user type in it, the property is updated. It allow us to control the contents of the input from inside and outside of the component.
The todo app also emulates that it is storing the todos in the server to show how to handle asynchronous reactions. Again it is really cool to control every todo status from outside, so we can show informative messages to the user while the server replies. Let’s see the code of the todo update reaction:
Immutable data boosts your app performance
You might be thinking that having the input values outside the components will re-render your whole app everytime the user types. Fortunatelly, using Freezer also allows to skip useless re-render checks. Not having an internal state, our components have pure render methods. It means that if the props don’t change, calling the render method is not needed.
Since our properties are immutable, check if there has been some change in the data is as easy as using a plain comparison. Have a look at the TodoList component implementation.
Cheap undo and redo
This is the typical feature that is highlited when speaking about immutable data. See a JSON editor made with Freezer that offers undo/redo.
When the state is updated, all the nodes that were not changed are reused by the new state, so it is possible to save previous states and the memory usage is not going to increment drastically.
All the state in one place
Having all the app data in the same place let any part of the application know if something has changed elsewhere, so it is easier to coordinate the updates. But there are more benefits.
With all the data in one place it is easy to serialize and save it for later. That would let the user close the app and continue at the same point when he reopen it the following day.
Also, it is easier to inject the data in to the application making simpler to render the apps in the server side. Just recreate the data, and React will render the page to let you serve it without a browser.
Freezer and React are a great combo. They are easy to understand and take little effort to get used to work them. Why not to give them a try in your next project?