Developer Calvin Belden presents the first in our series of developer tutorials based off our experiences designing and developing ecommerce sites here at Adage. Follow us on Twitter or LinkedIn to make sure to catch all our developer resources hot off the press.
Modeling and updating your application’s state gets increasingly tricky as your app grows. More often than not, the state ends up being a haphazard conglomeration of global objects, and without any clear separation of concerns, this can quickly become a nightmare to maintain.
At Adage, we’ve been experimenting with a unidirectional architecture (in the same spirit as flux and redux) to build applications that are easier to reason about. There are two primary building blocks, reduce and Observables, and we'll cover each before putting together a demo app.
You can find the code demo source here.
Array.prototype.reduce accepts two arguments: the first is the accumulation function and the second is a seed value. The accumulation function is special because it is pure. As often as possible, we try to write pure functions because they’re testable and easy to reason about. Everything you need to know is in the function definition: we need not burden ourselves keeping track of global state.
The seed argument is helpful if we want to provide an initial value to our accumulator function. In the next example, we iterate over a list of ‘blue’ and ‘red’ strings. Each time we encounter one of these strings, we increment the appropriate counter. We pass in a starting value so we know that the blue and red properties will always be present on our accumulated value:
To prove that our click-counter application is working, we can add unit tests:
And because we don’t want to accidentally mutate any shared state, we’ll refactor our click accumulator so it returns a new object each time it’s executed:
To recap: reduce is a function that allows us to accumulate a return value. The meat of the operation is performed in a pure accumulator function, which is testable and easy to reason about.
I’ll briefly introduce Observables in this next section.
Quick disclaimer: if you haven’t worked with Observables, I highly recommend heading over to Andre Staltz’s introduction before proceeding.
An important main idea here ➫ RXJS allows us to treat events as data.
Creating Observable sequences
In the following code snippet, we create and subscribe to an Observable. All Observables have a subscribe method that allows us to register a callback to handle any data (i.e. asynchronous events) that gets pushed on to the observable.
In the previous example, we used a nifty helper method, from, that allows us to turn any iterable into an asynchronous stream. We can convert iterables to Observables, however, Observables really shine when used with asynchronous events. In the code below, we illustrate how to make an Observable of user clicks:
Earlier we investigated Array.prototype.reduce; Observable.prototype.scan operates very similarly, except it operates on Observables instead of Arrays. Just like reduce, scan accepts an accumulator function and a seed value. The return value of scan is a new Observable sequence! This sequence will emit each intermediate result of the calls to our accumulator function. To illustrate this, I’ve modified one of the code examples above to keep track of the number of times a user clicks:
Putting it together
We have a pretty good idea about how reduce works, and why we might want to use it (it allows us to use pure functions to perform aggregations). We’ve also seen how Observables allow us to treat user actions as sequences of data and we checked out reduce’s cousin scan! I’ll now walk through an application architecture that uses these constructs scan over user clicks and create a counter.
In the click-counter example, we performed a reduce over a list of numbers; the meat of that program lived in the accumulator function, and we showed how we could test that function to ensure it was well behaved. In this architecture, the accumulator function is the application logic and the list of “things” we accumulate over are actions.
We will refactor the code above to make our application more structured. We can do this by creating an ES6 class to represent our application. This class will have a reduce function (our application logic) and an interface for triggering actions. We will keep track of two private Observable sequences: one for keeping track of user actions and one for updating our application state:
To demonstrate how you might subscribe to state updates and re-render your application, we can imagine that we have two UI components that need to subscribe to our click counter state: a score board and some messaging that shows the current “winner”:
If we were using Angular or React, we might have our top level component subscribe to state updates and then pass any updated values to child components. In this example, we'll simply have each component subscribe to the state updates and render the new content:
Modelling the application as an aggregation of user actions allows us to clearly define the boundaries and capabilities of our application. It affords us the luxury of writing extremely testable and maintainable code; I can define a test suite to describe all behavior of the application because our application is a single, pure function.
If this overview tickled your fancy, you should check out Dan Abramov’s redux library. Redux provides the infrastructure for building larger applications in this same manner. It has some very impressive developer tools (featuring time travel debugging!) and excellent support/integration with React.
Calvin Belden is a Developer at Adage Technologies. Calvin holds a degree in electrical engineering from the University of Notre Dame and has worked on projects for some of Adage's most notable clients, including the American Academy of Pediatrics.
Join Adage's team of talented developers to polish your skills while you build industry-leading sites for exciting clients:
View our job opportunities