Om Next Normalization

get in there my son!

It’s hard to work out where to start with Om Next. The docs are starting to build up nicely but it’s still a steep learning curve, or at least it was for me!

Application state is held in a global atom in Om Next. When you load your app, typically one of the first things you will do is populate that global atom with your initial state, so I’m going to start there. And the key, shocking thing here is that Om Next’s normalization means that when you read your app state it may not be in the same format as how you initialised it!

Obviously, you are going to need to understand what format you application data is in or you may have a few issues working with it — and that is what this post is about.

Setup

If you want to work through the examples in this post, you should start by cloning the om-chat-base github repository

git clone https://github.com/colinf/om-chat-base

To check that it’s working, from the root directory of the cloned repository run the following command to start the server

lein run -m clojure.main script/figwheel.clj

Now if you launch your browser at http://localhost:3449 you should see a rather uninspiring, largely blank web page which says Thread Section… and Message section… at the top. If you can see that, you are ready to go.

Data In

All the code for this post is in the file src/om_chat/core.cljs so I suggest you open that now in your emacs. (Other editors are available!)

After the namespace declaration just about the first code you will see is the definition of the raw-data variable. This sets up the raw data pretty much exactly as per the original flux-chat example data.

At the end of the core.cljs file you’ll find the Om Next code to define a reconciler based on this raw data and then to add a dom root for the reconciler to control. These are pretty much the boiler-plate to setting up any Om Next app — if you’re not familiar with them yet then don’t worry about it for now.

The data is actually added to the reconciler using the :state member of the initialisation map. My code actually manipulates the raw data before adding to the reconciler — this is the code {:threads (reduce threads [] raw-data)}.

What this code does is re-format the raw data with more clojure-like variable naming conventions and also to create a hierarchical structure of threads and messages, rather than just being a list of messages with thread details included for each message. Have a look at the threads function in the core.cljs file by all means, but perhaps the easiest way to undertand it is to look at the output of it.

Leave your browser open and switch back to the command line where you started the server. You should see a prompt along the lines of cljs.user=>. This prompt is for a REPL executing in your browser. At the browsr REPL prompt enter the following command to switch into the om-chat namespace

(in-ns 'om-chat.core)

You can now find out exactly what data is passed to the reconciler by entering the code that is passed to :state in the core.cljs file and pressing enter.

{:threads (reduce threads [] raw-data)}

To make it easier to read here is a formatted version of what is returned. Hopefully you can see that it a fairly straightforward vector of threads with the messages related to each thread included as an embedded vector at :thread/messages

Data Out

So now let’s retrieve the application state from the reconciler to see what it has done with it. When I first used Om Next I was kind of expecting to get back what I added — but this is one of those rare moments in life when you get back more that you put in!

At the browser REPL prompt enter the following to retrieve the data from the reconciler

@reconciler

Below is a formatted version of what should be returned. It’s well worth spending a few minutes to study it to see how it is has changed from the data we added to the reconciler.

Nb you can use the command (cljs.pprint/pprint @reconciler) in your browser repl if you want to get the prettier output yourself.

At :threads/by-id and :messages/by-id respectively you can see the threads and messages stored in maps indexed by their id. All of the relational links are represented by vectors such as [:messages/by-id “m_4”].

So to sum that up, the data has indeed been normalized — consistency has been imposed and redundancy removed. And to add bonus to benefit, those vector links are the perfect format, no need for any manipulation, to be used in the clojure get-in statement.

So for example if we want to follow the link to message id “t_4” the following expression at the command prompt will illustrate how easy it is to get-in there!

(get-in @reconciler [:messages/by-id "m_4"])

Magic?

So we have looked at how Om Next’s normalization works and along the way seen some of the power of the browser REPL to look at your application state.

Did all this happen by magic I hear you ask? Well, not as far as I can tell. In fact Om works it out based on certain properties of your Om-based React components. And I’ll look at that in a separate post coming soon.

Nb if you find this type of magic spooky and desire full personal control of your precious application state, then pass an atom to the reconciler and you will bypass Om’s normalization