A case for ClojureScript when developing data-heavy single page applications

Introduction

This is the point where you turn to Redux. You’ve read Dan Abramov’s article “You Might Not Need Redux” and you’ve decided. You definitely need Redux. Now you’re thrown into a (perhaps unfamiliar) functional state management paradigm. At this point you can feel as though you’re at odds with JavaScript as a whole — You’re told your reducers must be functionally pure. That’s fine, but what’s stopping you from throwing a whacking great side effect into your reducer, or mutating your objects directly?

Honestly? There’s nothing stopping you. And that’s absolutely fine for JavaScript to allow these kinds of things. JavaScript is a playground where anything goes — it won’t sneer at you as you use a for-loop or forEach rather than rather than map to perform a function over a list. In my opinion, there's nothing wrong with this. This is what makes JavaScript… JavaScript. It's malleable and forgiving; It's likely to have the ability to accommodate your development needs, regardless of what background you come from.

But, let’s consider a few things: React as a library is focused around composability of visual components, where state shouldn’t be mutated outside of the function this.setState(newState). React is also named as such for a reason. It reacts to changes in application state and updates the UI appropriately. It encourages a functional programming style when updating application state. And now we're turning to a state management library that values immutability and functional purity as a top priority. So, why are we using a language that allows us to go against this so easily?

Enter ClojureScript — where pretentious programming buzzwords such as functional purity, immutability and composability aren’t an afterthought but the basis of the entire language syntax.

Clojure(Script)

  1. It’s a Lisp (honestly, that’s a good thing!) that runs on the JVM (for Clojure) or compiles to JavaScript (for ClojureScript).
  2. It’s a functional language
  • Data is immutable by default
  • Functions are treated as first-class citizens.

3. ClojureScript uses the Google Closure compiler
Your web applications can be optimised heavily, resulting in minimal load times.

You’ll notice here that many of the buzzwords mentioned earlier are also repeated here. Perhaps the most important part of this brief list is that Clojure is a Lisp — the language is homoiconic. This means that the language is (at its core) a composition of data structures and atomic values. React is built using declarative components where data is king. Can you see where I’m going with this?

Let’s consider a real world example for creating Single Page Applications using ClojureScript.

Re-Frame

  1. Event dispatch — The user has triggered an action, be it pressed a button or typed into a an input field.
  2. Event handling — A registered event handler listens for this event and “catches” it. Here we describe in a declarative manner the changes (and any side effects) to be made to the application database.
  3. Effect handling — Here the side effects are dealt with (this is the part where the functional programmers put their fingers in their ears, close their eyes and sing loudly). The important bit to note here is we compute and return the new application state. Think of it like a reducer that is elegantly able to deal with side-effects.
  4. Querying — We define how we extract data from the application date to send to the view (in the form of a subscription).
  5. The View — The view function receives the updated application state and so generates a different output.
  6. DOM — The changes in our view ultimately result in changes being made to the DOM.

In Redux this kind of architecture is materialised as action creators that trigger actions that are received by a reducer. One of the biggest pains of this is you end up introducing a lot of boilerplate to make a single change to the application state.

If we are looking to make a single change to application state this is already starting to look quite lengthy, and this only gets amplified when we decide we want to conditionally trigger actions based on certain criteria (this is where Redux Saga and Redux Thunk come in handy).

Here we are connecting our components state to the application store using connect(mapStateToProps, mapDispatchToProps, mergeProps, options), which has the problem of feeling quite bloated (at least to me), and often is a source of pain for developers new to Redux.

An interesting point to bring back here that was mentioned in the introduction is JavaScript’s flexibility: Yes, it’s nice that JavaScript is familiar to everyone but when you are dealing with code like this (where your reducer needs to be side-effect free) the amount of places where a developer could trip up is too high.

There’s certainly things that you can do to mitigate these potential gotchas, such as including component.propTypes to declare the props you're looking to receive (which I hope you're all doing anyway!), or using a library like immutable.js to make it actually impossible to mutate application state.

The problem here is the more third party solutions and middlewares you bring into the frame, there’s more libraries to learn, more code to maintain, and all around more logic to account for to ensure that your dataflow is working as intended. Wouldn’t it be nice to have a complete out-of-the-box solution that doesn’t leave us needing to add external tools to give us a complete feature set?

Now lets consider a similar example, but in ClojureScript (with Re-Frame).

The benefits to using ClojureScript may not be immediately obvious here but I’ll go over how I consider this code to be a real step up over the Redux example.

This code is very close to being something you could actually run: We are missing only a few pieces of code. These include initialising the database (which is done through another dispatch action done only once when we start up the application) and the mandatory mounting of the UI to a DOM element using reagents render function. If we were to consider this from a purely line quantity perspective (I know some people are quite evangelical to the idea that less lines == less places for bugs, therefore better code) then Re-Frame certainly has an edge over Redux.

You don’t have to worry about accidentally introducing side effects: With Redux you need to be extra careful that your reducers are functionally pure. Many newcomers to Redux often make the mistake of using array.push(newItem) to update their application state rather than array.concat(newItem). With Re-Frame you're not able to make these mistakes. The responsibility of the effect handler is simple – the value that you return from your rf/subscribe function is what the application database will be updated to be. Since we are dealing with an intrinsically immutable language the opportunities for developers to make mistakes such as this are reduced massively.

No more redux.connect: mapping dispatch and state to props using Redux is probably one of the most bloat worthy parts of Redux (to me, at least). With Re-Frame, we can subscribe to the value that we want by supplying the key to rf/subscribe, which returns an atom that we can dereference using '@' to access its state.

We can handle side effects without middleware: I mentioned before that we can use Redux Saga or Redux Thunk to allow our action creators to deal with side-effects, but this is yet more code that you need to add to ensure that Redux works how you want it. With Re-Frame you can change your event handler function call to rf/reg-event-fx which allows you to describe your side-effect ridden effect handlers in a declarative way (more information here).

To Summarise

Redux can also introduce a lot of unnecessary feeling bloatware to your application and if you want certain features (such as truly immutable application state or action creators with side-effects) you’ll need to turn to external libraries.

Re-Frame capitalises on the data-driven and concise nature of ClojureScript and uses these strengths to manage application data reliably and effectively. There is a lot more information that shows the benefits of using Re-Frame that I’ve not been able to cover here.

If you are interested I highly recommend trying out Re-Frame. Even if you don’t have experience with Clojure (I am still very much learning), the syntax is surprisingly simple to pick up. There’s plenty of well written documentation that includes some basic examples to help you get started.

Originally published at www.columbiaroad.com.

Nordic digital sales consultancy. We help companies get more revenue and more customers in the digital era.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store