Use namespaced keywords for events

Introduction

If you’re using re-frame as your pattern for single-page applications in ClojureScript (and why wouldn’t you?), then you are defining your events and handlers using keywords.

These are a prime example of when Clojure’s namespaced keywords come in handy.

An aside on keywords

If you’re just starting in Clojure, you have probably seen that you can define keywords with one or two colons - for instance, :click-handler and ::click-handler. The first one merely references a keyword, whereas the one with two colons implicitly includes the full namespace where it was created.

If your current namespace is relevance.core, for example, then using the later keyword would be equivalent to :relevance.core/click-handler.

You can think of them this way: :click-handler is just like passing around a readable version of a constant value (say, 42), while using ::click-handler is equivalent to defining that as a static value within a class and then passing that reference.

Why use namespaced keywords on re-frame

The issue

Suppose you have a basic handler like this one:

1
2
3
4
(re-frame/register-handler
:navbar-click
(fn [app-state [_ item-key]]
(assoc-in app-state [:ui-state :active-section] item-key)))

It gets fired when the user clicks on a navigation item, and does something trivial: it assigns an active UI section on the application state.

While re-frame is a framework for single-page applications, nothing stops you from using it on an application where you have multiple pages, each one with their own namespace for managing its user interface. re-frame does have a limitation that you may only define one handler per message. Fine, you think, my handlers are on different namespaces.

You go ahead and (dispatch [:navbar-click :main-menu]).

Everything works well while you’re on development, but when you build the application, some of your handlers don’t behave as you expect.

You dig up a bit, and it turns out the misbehaving handlers are precisely the ones where you use the same keyword on a different namespace. You have one UI per page, and by convention, you always trigger a UI state switch by dispatching on :navbar-click.

Congratulations, you’ve just tripped a scoping issue.

The root cause

As you’ve probably figured from my description about the keywords above, what is causing the problem is a combination of things:

  • Keywords without namespaces might as well be global constants
  • re-frame handlers tie to an event keyword
  • You can only have one handler per event keyword

When you do :advanced compilation on ClojureScript, all your namespaces end up munged together in a single file by default. This means that the initialization functions where your namespaces tie into a handler are all being invoked… and since you can only have one handler per event keyword, some get replaced.

While naked keywords are fine for the message payload, if you are defining and dispatching re-frame events from multiple namespaces, you’ll save yourself a lot of debugging time and mental bandwidth if you define them with namespaced keywords (since you won’t need to wonder if you have already defined the same event name elsewhere).

So you go to the code above, you change :navbar-click for ::navbar-click, and everyone’s happy.

Alternatives

This is not the only solution, of course. You can also treat each page as its own build task, like I outlined when talking about cljsbuild configuration for Chrome extensions. In fact I expect that’s what your final build will end up looking like for a case like this, but being disciplined about your event definitions saves you from having to define a build structure up front.

Conclusion

I hadn’t seen many applications for namespaced keywords on everyday development, other than message passing. It’s easy to forget that message passing is exactly what you’re doing on re-frame, so if you’re going to have more than one independent namespace, consider using namespaced keywords from the start.

Once you start thinking about naked keywords as integer constants, you can’t help but feel dirty about using them for something as specific as an event handler.


Published: 2015-12-07