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.
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.
Suppose you have a basic handler like this one:
1 | (re-frame/register-handler |
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.
As you’ve probably figured from my description about the keywords above, what is causing the problem is a combination of things:
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.
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.
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