Re-Frame - Functional Reactive Programming With Clojurescript

In this blog post, I’m going to walk you through the port of the Angular Phonecat tutorial in Re-frame - a very simple, yet expressive, library for architecting Single Page Apps in Clojurescript. Rather than discuss why use such a framework, I’ll let Mike’s excellent writeup on the project do the talking. Read it? Okay let’s get into building an app with phone-cat! We’re going to go step by step and mirror the exact steps taken in the official angular walkthrough. As a result, you’ll be able to compare each step against its analog in Angular and get a better feel of how these frameworks are different and what their strengths and weaknesses are.

Finally, each step has its own branch in this repository. So feel free to clone, fork, and follow along with the code for each step!

Step 0 - Setting up

Explanation

Okay let’s get started! Step 0 is where we set up our application. The steps are as follows:

Download Leiningen Run lein new reagent angular-phonecat-re-frame

Awesome! You should have the scaffolding for the app. Let’s try and understand the pieces of the scaffolding.

project.clj Project.clj defines the libraries your project depends on and its build settings. See here for more information.

src/cljs/phonecat_re_frame/ This is where most of our main application logic will go. During the build step, we are going to look at the cljs code here and compile it into javascript.

resources/templates This is where we will place our html.

resources/public This will hold the css, images, js, and other resources for our project. After the build compiles the clojurescript into javascript, it will place the javascript in a subdirectory of this folder.



Now go ahead and open src/cljs/phonecat_re_frame/core.cljs. This is where our main logic will go! I’m going to assume that you have a basic idea of reagent from here going forward. If you don’t check out my post on reagent where I go into further detail than I’ll have room for here.

Step 1 - A basic phone page

Explanation

In our first step, we just fill out our home page to show a little information about some phones. If you haven’t seen hiccup before this should be a good introduction. Hiccup allows us to define html elements using Clojure vectors. Let’s look at an example to understand how it works. In the current step, we added the following Hiccup Vector:

[ :ul [ :li [ :span "Nexus S" ] [ :p "Fast just got faster with Nexus S." ]] [ :li [ :span "Motorola XOOM with Wi-Fi" ] [ :p "The Next, Next Generation tablet." ]]]

This translates into the following html:

<ul> <li> <span> "Nexus S" </span> <p> "Fast just got faster with the Nexus S." </p> </li> <li> <span> "Motorola XOOM with Wi-Fi" </span> <p> "The Next, Next Generation tablet." </p> </li> </ul>

So what’s going on here? The first element in the vector is just the tag. So [:ul] indicates that we are creating a <ul> element. Next, we can nest elements inside other elements by nesting vectors. So [:ul [:li]] translates to <ul><li></li></ul>. To fill out the body of the element, we just add the body details after the tag name in the vector. So in our example [:span “Nexus S”] gives us Nexus S. Pretty neat right?

The awesome part about this is that our HTML is now a first class data structure that we can compose, return, and apply functions upon. Basically we have way more power over them! You’ll see how this will come in handy as we write some basic templating functions in future steps.

Step 2 - Using real data

Explanation

Now we are going to change our phones page to use data from a database that we setup. The re-frame architecture espouses the idea of storing all your application data in a single place. This isolates state mutations to a single place and also ensures that all your data is in sync. Ok enough talk. How do we actually set up this db?

Handlers

In re-frame, we make mutations to our db by creating handlers. A handler is just a function that takes in the current application state and some parameters and returns a new application state. What could be simpler?

We create our handler as an anonymous function and then register it by passing it into re-frame/register-pure-handler (pure handler just indicates that our handler doesn’t mutate state).

( re-frame/register-pure-handler :initialise-db ;; usage: (dispatch [:initialise-db]) ( fn [ db v ] ;; Ignore both params (db and v). { :phones [{ :name "Nexus S" :snippet "Fast just got faster with Nexus S." } { :name "Motorola XOOM™ with Wi-Fi" :snippet "The Next, Next Generation tablet." } { :name "Motoral Xoom" :snippet "The Next, Next Generation tablet." }]}))

So above, we initialize our db as a hashmap with a key :phones and a corresponding value.

In the above declaration, we have also given our handler a name :initialise-db that re-frame will use to lookup the handler.

Dispatchers

Now that we’ve created our handler, how do we call it? That’s done in this little bit:

( re-frame/dispatch [ :initialise-db ])

We call handlers by calling the re-frame dispatch function with the name of our handler. So in summary, here’s the process of what’s going on:

We call dispatch with a handler name. Re-frame looks up the handler and calls it with the current app data and any additional params you passed it. Re-frame updates the app data with the output of the handler.

This is going to be our flow for ALL events. Dispatch is called with a handler, the handler creates a new db, and we use that db going forward. But how does our app know when parts of the db have changed? We would need to update our views right?

Subscriptions

In Re-frame, we do that through subscriptions, which we use in the following code:

( re-frame/register-subs ;; a new subscription handler :phones ;; usage (subscribe [:phones]) ( fn [ db ] ( reaction ( :phones @ db )))) ;; pulls out :phones

Just like register-handler, register-subs takes in two arguments: a name, and a function. The function in this case returns an ratom that represents some part of the data (Don’t worry we’ll get into ratoms in a sec). We then use this subscription to always get access to the latest value of :phones in our db. We use it as below:

( let [ phones ( re-frame/subscribe [ :phones ])]

Now, whenever we call @phones, we will always have the latest value of phones in our database. The beautiful part about this is that @phones will automatically update when the underlying value gets changed in our database. Wow! That to me is pretty sick. Ok but what’s the dark magic going on here?

Our subscriber function,

( fn [ db ] ( reaction ( :phones @ db )))) ;; pulls out :phones

returns an ratom representing the latest value of :phones in our app database. This is achieved through using Reagent’s reaction function. Reaction takes in a function that depends on an ratom, and returns another ratom. Now whenever the value of the ratom we depend on changes, @db in this case, the function is recomputed and the value of the ratom that reaction returns updates also. So say I had the following:

( def db ( ratom { :a 1 })) ( def a-value ( reaction ( :a @ db )))

At this point, @a-value will be 1. If I update db and print a-value,

( assoc db [ :a ] 3 ) ( println @ a-value ) ; 3

@a-value automatically updates! Under the hood, reaction just creates a callback that fires whenever @db changes. It then sets resets the output of its returned ratom to be the result of calling this callback on the new @db.

View Layer

We’ve also updated our view layer in a few important ways. First, we’ve created a separate function that is responsible for displaying each individual phone item. We see it here:

( defn phone-component [ phone ] [ :li [ :span ( :name @ phone )] [ :p ( :snippet @ phone )]])

Check out how freaking easy it is to create separate view components! This component just takes in a phone ratom and displays its name and snippet. Simple. We then also create a component for displaying the phones list and have it use the phone-component we defined above:

( defn phones-component [] ( let [ phones ( re-frame/subscribe [ :phones ])] ; subscribe to the phones value in our db ( fn [] [ :ul ( for [ phone in @ phones ] ^ { :key phone } [ phone-component phone ] @ phones )])))

In the above snippet we first subscribe to the phones value in our db. Then, we use the power of hiccup to iterate over each phone in our database (for [phone in @phones]) and display a separate phone-component for it. We use the phone-component function by calling [phone-component phone]. To understand how this works see Mike Thompson’s awesome explanation of Reagent components here. Note how we don’t need to use any new templating language - we just use clojurescript!

Step 3 - Search

Now let’s add a little search box that allows us to search for phones and just display the ones we are interested in.

Handlers

As in the previous step, we start by adding in a handler that fires when a user types something into the search box. This handler will update our app data with the new search term.

( defn handle-search-input-entered [ app-state [ _ search-input ]] ( assoc-in app-state [ :search-input ] search-input ))

( re-frame/register-pure-handler :search-input-entered handle-search-input-entered )

We also create a new subscriber to get the latest search term from the db:

( re-frame/register-subs :search-input ( fn [ db ] ( reaction ( :search-input @ db ))))

View Layer

Next, we create a simple search component that calls our new handler whenever the user types something in:

( defn search [] ( let [ search-input ( re-frame/subscribe [ :search-input ])]) ( fn [] [ :input { :on-change # ( re-frame/dispatch [ :search-input-entered ( -> % .-target .-value )])}]))

Note how we are passing a value into our handler function by giving dispatch a vector of two items: the first is the name of the handler, the next is the additional argument to that handler.

Finally, we update our phones-component to only show phones that match the search term in some way:

( defn matches-query? [ search-input phone ] ( if ( = "" search-input ) true ( boolean ( or ( re-find ( re-pattern search-input ) ( :name phone )) ( re-find ( re-pattern search-input ) ( :snippet phone )))))) ( defn phones-component [] ( let [ phones ( re-frame/subscribe [ :phones ]) search-input ( re-frame/subscribe [ :search-input ])] ( fn [] [ :ul { :class "phones" } ( for [ phone ( filter ( partial matches-query? @ search-input ) @ phones )] ^ { :key phone } [ phone-component phone ])])))

Here, we use Clojure’s filter function to just filter our phones vector with the function matches-query?. Note how we don’t have to create any messy callbacks or anything. @search-input automatically updates with the new value!

matches-query just uses a regexp to check if the search term is present in the name or snippet of the phone. Again, notice how we don’t have to define any special filters in our template. We are using plain old Clojure code.

Step 4 - Sorting our phones

Step 4 is almost identical in nature to step 3. We are going to store an ‘order-by’ property in our app-db and use it to sort the phones in the list.

Please enable JavaScript to view the comments powered by Disqus.