The Data Handbook
How to use data to improve your customer journey and get better business outcomes in digital sales. Interviews, use cases, and deep-dives.
Get the bookIntroduction
You've started to write your first React application – great! You're amazed how simply you can create and reuse components and all is good with the world. You then begin to write more components, and decide to try and manage your component state in containers, passing around props to child components to maintain simplicity (as per Thinking in React). This works fine for a while… but then things start to get a bit messy. Your data is dispersed throughout your application and it becomes a juggling act to keep everything organised.
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)
I won't attempt to give Clojure justice by describing it here; I would recommend reading this if you want to find out more about the language a bit more in depth. But to summarise:
- It's a Lisp (honestly, that's a good thing!) that runs on the JVM (for Clojure) or compiles to JavaScript (for ClojureScript).
- It's a functional language
• Data is immutable by default
• Functions are treated as first-class citizens. - 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
Re-Frame is a "buzzword compliant" framework for developing SPAs in ClojureScript, that uses Reagent (a "minimalistic interface between ClojureScript and React"). Re-Frame aims to leverage the benefits of using ClojureScript (as mentioned above) for state management by introducing a perpetual loop of consequential actions (where one triggers the next, that triggers the next, and so on). If you are familiar with Redux this loop of actions will be familiar to you, but I will quickly summarise each individual action here briefly:
- Event dispatch - The user has triggered an action, be it pressed a button or typed into a an input field.
- 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.
- 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.
- Querying - We define how we extract data from the application date to send to the view (in the form of a subscription).
- The View - The view function receives the updated application state and so generates a different output.
- 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 is pushing the JavaScript ecosystem in the right direction when it comes to data management. This style of programming enables you to be deterministic and consistent when dealing with application data. But, JavaScript is the wild west of programming languages – anything goes and there's little stopping you from making questionable decisions.
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.
P.S. We're looking for nice people with a 'get shit done' attitude to join our team at Columbia Road! Take a new leap in your career, we have open positions in all business areas. Send a message, call us, visit us — we'd be thrilled to hear from you!
The Data Handbook
How to use data to improve your customer journey and get better business outcomes in digital sales. Interviews, use cases, and deep-dives.
Get the book