Combining Flux and FRP: Hello ffux!

Matti Lankinen
7 min readJun 30, 2015

Taking the best parts from both worlds

I wrote an article about how to replace Flux with Functional Reactive Programming about two months ago. It received a lot of good feedback and started some interesting discussion. Despite that, there are not so many React + FRP implementations yet. What should be done differently so that people would start using FRP with React?

The mental model of FRP differs so much from “traditional” programming models that it’s hard to approach by newcomers

Although Functional Reactive Programming gives some powerful tools for state and flow management, the mental model of FRP differs so much from “traditional” programming models that it’s hard to approach by newcomers. Flux does this so much better: it has a commonly known terminology and clear mental model, and that’s why it is easy for new developers to step in and give it a try.

I’ve been thinking that there must be some kind of way to combine the best parts from both worlds. After hours and hours of trial and error I figured it out how to preserve the terminology and mental model from Flux but to harness the power of FRP at the same time. So say hello to ffux, RxJs and Bacon.js compatible Flux implementation!

Yet another Flux implementation?

Yes. And no. The core concepts are exactly same in ffux as in all other Flux implementations: interactions are done through the action creators that emit actions which are listened by the stores. However there is one major difference: actions in ffux are event streams (Bacon.js) / observables (RxJs) belonging only to the specific stores.

In Flux, the separation of action creators and stores is meant to achieve loose coupling since any store can listen any action. But this is a two-bladed sword. Separated action creators do not tell directly which stores relate to them, and this leads to the “logical business units” (store + its action creators) having low cohesion.

Anything can happen when you emit an action without knowing its implications — in the worst case it may produce a circular call chain (or deadlock).

In practice this may lead to situations where it is hard to predict the implications of a single action creator call (especially when your application and the number of inter-store dependencies grow). Anything can happen when you emit an action without knowing its implications — in the worst case it may produce a circular call chain (or deadlock). Not very easy to maintain, in my opinion.

Loose coupling is definitely one of the best parts of Flux. But how to preserve it while dealing with the low cohesion at the same time? This is the place where to combine Flux with FRP: we can provide interactions by using actions and preserve the state in decoupled stores but also model the entire application state flow by using FRP, thus making it explicit and predictable. In order to make this possible, ffux has three core ideas:

  • Everything “inside ffux” is an event stream
  • Actions are dedicated to specific stores
  • Dependencies must be declared explicitly

Everything is an event stream

Image by Tiago J. G. Fernandes (CC BY 2.0)

I know. The concept of event streams may look difficult at the beginning but after you understand them, you have an ultimate tool against asynchronous state handling (which is inevitable when dealing with real-life applications). Like Functional Programming, FRP provides a set of higher-order functions that can be used to model your problem, whether it’s synchronous or not.

With ffux, all the power of Functional Reactive Programming is on your hands.

There are plenty of good FRP tutorials so I don’t go into FRP details in this article. The main point is that with ffux, all the power of Functional Reactive Programming is on your hands. You have an absolute freedom to choose how you’re gonna model the state flow in your stores - ffux just provides the event streams from store’s actions and dependencies. In return, ffux expects the store to return an event stream. Everything before that is up to you.

How would you implement a pessimistic item creation with a server integration? In ffux, it could look like this:

// items.js
const Rx = require("rx")
const {createStore} = require("ffux/rx")
const {fromPromise} = Rx.Observable
export default createStore({
actions: ["createItem"],
state: (initialItems, {createItem}) => {
const okToCreate = new Rx.BehaviorSubject(true)
const create =
createItem
.withLatestFrom(okToCreate, (text, ok) => ({text, ok}))
.filter(({ok}) => ok)
.map(({text}) => text)

const created =
create
.flatMap(text => fromPromise($.ajax(..post text..)))
.catch(Rx.Observable.return({error: true}))
// "create" change the okToCreate -> false
// "created" reverts it back to true
create.delay(0).map(false).subscribe(okToCreate)
created.map(true).subscribe(okToCreate)
// only non-error "created" item is appended to the items
return created
.filter(({error}) => !error)
.scan(initialItems, (items, item) => [...items, item])
.startWith(initialItems)
}
})
// ...and calling it from the React View
const {createItem} = this.props.actions
createItem("tsers")

And did I already mention? The usage of event streams completely removes the need for waitFor.

Per-store actions

In ffux, store’s actions belong to that store. And that store only. In this way, you always know what is the primary effect of your actions. This follows the single responsibility principle: every action should have only one responsibility like “createItem”, “resetFilter”.

In ffux, the action creation is extremely simple: to introduce a new action, its name must be added to the store’s actions array. After that, the action is listenable in the store as an event stream that is triggered every time when the corresponding action creator is called (e.g. from the user interface):

// counter.js
const ffux = require("ffux/rx")
const {createStore} = ffux

export default createStore({
actions: ["increment", "reset"],
state: (initialState, {increment, reset}) => {
// increment and reset are now Rx.Observables that can be
// used to construct the state of this store
}
})

Although the actions are event streams in the store’s scope, the action creators are normal functions when the store is added to the ffux dispatcher’s scope. And since the action creators are just functions, you can’t bind any state into them (or apply bidirectional data flow):

// app.js
const ffux = require("ffux/rx"),
Counter = require("./counter")

// create dispatcher with counter store (initial value = 10)
const dispatcher = ffux({counter: Counter(10)})
dispatcher.listen(({state, actions}) => {
// in this scope, actions are normal functions
const {increment, reset} = actions.counter
increment(2) // they can also have parameters
reset()
})

Explicit dependencies

Explicit dependencies help you to identify how the application works in the higher abstraction (“it seems that displayed items depend on filter” and so on). In ffux, stores are immutable. Because of that, the only way to define dependencies is to provide them during the store’s instantiation. Implications: no circular dependencies. Never again!

There is also another and even more important reason: when you must define your dependencies explicitly, you are forced to think about the architecture of your application — its relations and responsibilities. If your application requires a circular dependency, it might indicate some flaw in the architecture.

When you must define your dependencies explicitly you are forced to think about the architecture of your application — its relations and responsibilities.

But what happened to the decoupling? Even though you must define the dependencies explicitly, it does not mean that you must abandon the decoupling of stores: ffux stores do not know what is the origin of their dependencies:

// items.js
const {createStore} = require("ffux/rx")

export default createStore({
actions: ["createItem", ...],
// items store has no idea where the filter comes from!
// it just tells that it depends on filter
state: (initialItems, {createItem}, {filter}) => {
// since filter is a normal Observable,
// it can be used just like action streams
}
})

Because the dependencies are introduced during the instantiation of stores, any store can be given as a dependency to the other stores but cross-dependencies are not possible. However, one store can depend on multiple stores:

// todoApp.js
const ffux = require("ffux/rx"),
Filter = require("./filter"),
Items = require("./items")

const filter = Filter("")
// displayed items depend on the applied filter value
const items = Items([], {filter})

ffux({filter, items}).listen(...)

But isn’t this against the “rules” of Flux?

Nope. If you look at the Flux data flow diagram, you don’t see any violations. Store’s actions-array generates a set of action creators that emit actions. These actions flow through the ffux dispatcher to the stores and finally the stores emit the changed state to the views. Simple and easy to understand — just like Flux is meant to be.

Image by Facebook

Is it isomorphic, hot-reloadable, …?

Yes. It is. Or at least it will be. The library is still in progress but if you got interested, you can join the development and contribute to the project in GitHub. It’s definitely not ready yet but hopefully in future it may be the top library when creating applications with React, Flux and Functional Reactive Programming.

All ideas, thoughts and opinions are welcome. Join the development discussion now in Gitter!

--

--

Matti Lankinen

Software development, simple solutions and clean code. At @ReaktorNow