In a small app I'm building that uses Reagent and Re-frame I'm using multi-methods to dispatch which page should be shown based on a value in the app state:
(defmulti pages :name)
(defn main-panel []
(let [current-route (re-frame/subscribe [:current-route])]
(fn []
;...
(pages @current-route))))
and then I have methods such as:
(defmethod layout/pages :register [_] [register-page])
where the register-page
function would generate the actual view:
(defn register-page []
(let [registration-form (re-frame/subscribe [:registration-form])]
(fn []
[:div
[:h1 "Register"]
;...
])))
I tried changing my app so that the methods generated the pages directly, as in:
(defmethod layout/pages :register [_]
(let [registration-form (re-frame/subscribe [:registration-form])]
(fn []
[:div
[:h1 "Register"]
;...
])))
and that caused no page to ever be rendered. In my main panel I changed the call to pages
to square brackets so that Reagent would have visibility into it:
(defn main-panel []
(let [current-route (re-frame/subscribe [:current-route])]
(fn []
;...
[pages @current-route])))
and that caused the first visited page to work, but after that, clicking on links (which causes current-route to change) has no effect.
All the namespaces defining the individual methods are required in the file that is loaded first, that contains the init function, and the fact that I can pick any single page and have it displayed proves the code is loading (then, switching to another page doesn't work):
https://github.com/carouselapps/ninjatools/blob/master/src/cljs/ninjatools/core.cljs#L8-L12
In an effort to debug what's going on, I defined two routes, :about
and :about2
, one as a function and one as a method:
(defn about-page []
(fn []
[:div "This is the About Page."]))
(defmethod layout/pages :about [_]
[about-page])
(defmethod layout/pages :about2 [_]
(fn []
[:div "This is the About 2 Page."]))
and made the layout print the result of calling pages
(had to use the explicit call instead of the square brackets of course). The wrapped function, the one that works, returns:
[#object[ninjatools$pages$about_page "function ninjatools$pages$about_page(){
return (function (){
return new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"div","div",1057191632),"This is the About Page."], null);
});
}"]]
while the method returns:
#object[Function "function (){
return new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"div","div",1057191632),"This is the About 2 Page."], null);
}"]
If I change the method to be:
(defmethod layout/pages :about2 [_]
[(fn []
[:div "This is the About 2 Page."])])
that is, returning the function in a vector, then, it starts to work. And if I make the reverse change to the wrapped function, it starts to fail in the same manner as the method:
(defn about-page []
(fn []
[:div "This is the About Page."]))
(defmethod layout/pages :about [_]
about-page)
Makes a bit of sense as Reagent's syntax is [function]
but it was supposed to call the function automatically.
I also started outputting @current-route
to the browser, as in:
[:main.container
[alerts/view]
[pages @current-route]
[:div (pr-str @current-route)]]
and I verified @current-route
is being modified correctly and the output updated, just not [pages @current-route]
.
The full source code for my app can be found here: https://github.com/carouselapps/ninjatools/tree/multi-methods
Update: corrected the arity of the methods following Michał Marczyk's answer.
defmethod
in some root namespace? Because if you don't require those namespaces explicitly, your methods simply don't get added to the multimethod. – Inundate