React Made Reactive: Components as Observables
Over the past year of developing applications using React (both at work and at home), I have had the opportunity to see a steady progression of how I like to architect my projects: from each component having local state (and managing that mess to the best of my ability), to using Flux, and now on to experimenting with Redux and other libraries to try to fill in the limitations and pain points of Flux’s original idea.
The crux of the issue is that most UI’s are not hard to code, but they very quickly become hard to manage. Even implementing simple features, like drag and drop, or autocomplete, can at first pass be difficult to do in a declarative and maintainable way.
In the pursuit of reducing the complexity of our applications, a term kept popping up over and over: functional programming.
Pure functions in the View
The gist of it is taking a component like this:
And instead of the component managing its own internal state, refactoring it to render based on the props that are passed down to it.
In fact, this can now be further refactored into a stateless function component:
We still need to solve where to keep track of state (the `name` property of our application), though. If not in the component, then where?
Models and stores
Abstracting state away from the presentation of our applications is not a new idea by any stretch; the Model-View-Controller design dates back to the 1970s, and many more iterations (MVVM, MVP, …) have come out of this paradigm. However, the origins of MVC is rooted in Object Oriented thinking, which clashes with our desire to keep the view completely pure.
Unidirectional data flow is the term most often used around React and its accompanying libraries. I was first introduced to this concept through Flux, a design championed by Facebook as The Way(tm) of maintaining state. They do a better job than I could of explaining exactly what Flux is and how it works.
What I eventually came to the conclusion of was that Flux was OK… but not perfect. My code still became confusing after awhile: what constituted its own store? How do I elegantly deal with stores depending on one another? Where do my AJAX requests go??? Plus all of the boilerplate to define stores, actions, constants… at the end of the day, writing an app using the Flux architecture just felt like a lot of work.
Then Redux came along, which answered some of these questions: One store, action creators (allowing asynchronous actions to be reasoned about easier), and reducers helped separate the complexity of managing state changes from the store itself.
However, I still felt bogged down by the amount of boilerplate required to get started with Redux. I’m sure that at some point the amount of abstraction becomes necessary, but having to write and keep track of stores, then actions, then reducers… a project became a lot of work to write and maintain, and can seem daunting to get up to speed on if you’re not already very familiar with the project architecture. I felt like it shouldn’t take that much effort to map a button click to a change in state.
Mapping a button click to a change in state
Enter functional reactive programming.
Again, other people have done a much better job than I of explaining many of these concepts. However, for our front-end applications built in React, we can see the difference once we build out the rest of our Hello app. Here is how we could write it using a faux-Flux implementation:
Or, if we could use reactive observables for the same level of encapsulation:
To me, this way of modeling applications — as pure transformations of UI events to state — is basically what Flux/Redux is driving towards. This is exactly what functional reactive programming is about! However, by modeling UI events as streams, instead of callbacks tied to event handlers, we are able to subscribe to them explicitly instead of being required to write lots of boilerplate for things like “action creators” and “stores”. It inverts who’s responsible for defining actions: in the Flux/Redux pattern, our UI emits (often multiple) actions. When our components are observables, our actions subscribe to our UI.
From the other side, I believe that this way of combining observables with a component interface (instead of using class/html selectors, a la Cycle.js) is incredibly powerful. It allows us to encapsulate our intents much more easily, and is just a more expressive way to define our UI and following transformations. We also still have the ability to utilize local state when necessary — I am not so naive as to think that Functional Reactive will always be the best solution, but this way we can “dirty” our pure views on our terms, not because we have to.
If you’re wondering where the `fromComponent` function came from in the last example, it’s not something that comes standard in React or most (if any) reactive programming library. For this reason, I have developed a small library to provide this functionality: observe-component. Using observe-component, we can use the entire React event system as reactive streams, allowing us to fully leverage functional reactive programming architectures.
In part 2 of this blog post, I will show an example of how to implement a simple drag-and-drop application using observe-component and the Model-View-Intent architecture championed by Cycle.js and other functional reactive frameworks.