Flux inspired reactive data flow using React and Bacon.js

November 18, 2014

Read time 4 min

A few years ago I got introduced to reactive programming and Bacon.js, a FRP (functional reactive programming) library for Javascript. I got fond of it very fast, as it allowed you to create highly reactive UI’s with ease. While it works great in propagating changes to your views, it was still rather cumbersome to attach all the data flows to individual DOM nodes and properties.

React solved that problem completely. React makes the DOM reactive to your applications state, making your views predictable and declarative. Effectively, it allows you to create immutable views which get re-rendered each time the state of your application changes. However, recreating the entire DOM of your application for each state change would be far too expensive to be usable. React solves this problem by implementing a virtual DOM, which it uses to diff the changes that occur to the DOM, so that it’ll only perform the minimal changes it needs to make the DOM up-to date.

Flux is an application architecture commonly used with React. The architecture is based around unidirectional data flow. The Flux architecture fits well with Bacon.js and allows you to easily keep the application logic separate from individual views reducing coupling in your application. Facebook has a great introduction to the Flux architecture, so I won’t go into more detail regarding the basics of it and instead focus on how it can be applied with Bacon.js.
Flux architecture

Flux architecture diagram (Source: Facebook)

Mixin’ Bacon with React

My goal was to make my React components update their state automatically when one of their underlying Bacon streams would update. Similarly to the Flux architecture, I also wanted to keep my EventStreams (action creators) and stores away from my components, so that the application logic would reside with its logical domain for better cohesion. To keep the logic as simple and functional as possible, I opted to use the Immutable.js library for storing the data. Immutable.js provides persistent immutable data collections for JavaScript.

In your typical Flux setup, you would have a single central dispatcher which would transport all actions from your action creators to your stores. While this pattern could easily be followed with a Bacon.js implementation, it felt redundant as you can make each action an individual EventStream that can be subscribed and merged by stores as necessary. As a result, I attached each action creator method to form a separate EventStream.

The stores could then subscribe to the EventStreams (actions) that were relevant to them and perform the necessary updates to the store. As the changes to the store in itself results in another EventStream, it can easily be filtered, sorted, and mapped as necessary to the views, and exposed out for the views to subscribe to. Below is the full store logic for the todomvc application (which was based on Facebooks flux todomvc example):

To simplify the binding of EventStreams to React components, I created a small mixin that updates the state of the component on updates to the stores EventStreams. The mixin takes care of applying the initial state (if synchronous), applying subscribers that update the state when the stores update, and cleaning up the subscribers when the component unmounts. Making the state of the components easily bound to the stores:

stateFromStores: stores => ({
    allTodos: stores.todo.getAll,
    filteredTodos: stores.todo.getFiltered,
    areAllComplete: stores.todo.allComplete,
    filter: stores.todo.getFilter
})

Isomorphic applications

Another goal I had was to simplify the creation of isomorphic applications with React. One of the common problems with doing this with React is that the library doesn’t provide any asynchronous methods for loading state. Unless you want to use fibers with something like react-async, the state of the application must be ready before you start to render the React application into markup. This means that stores must be populated with the required data before you can proceed to render it.

With having all your stores and component states as EventStreams (or any deferred computation for that matter), it greatly simplifies the process for loading the necessary data prior to proceeding with the render.

Conclusion

Replacing the EventEmitter with EventStreams from Bacon.js allowed me to make the application stores far simpler and purely functional. With the store mixin, keeping the components state up to date with the stores was easy as well. Creating generic EventStreams (effectively the same as Bacon.Bus) as action creators is a bit of an anti-pattern. However, in contrast to having the EventStreams created directly inside the components, by having them detached in the action creators it results in less coupling and makes testing easier.

The stack I used has three distinctive libraries in use; Bacon.js for transporting data and events, React for rendering the data into DOM, and Immutable.js for maintaining the data. With the relative low coupling that the Flux architecture provides, any one of those libraries that I used could be switched with relative ease if necessary.

I recreated the two Facebook Flux examples using Bacon.js:

Never miss a post