Reagent: Does defining a component inside another component cause performance issues? - clojure

In Reagent, let's assume that I am defining a helper-function that returns a child component within a render function of the parent component.
Does this cause new child components to be generated every time the render function runs?
Here is a minimal example to illustrate:
(defn ChildComponent [text]
[:p text])
(defn ParentComponent [names-vector]
(let [renderChild (fn [i]
[ChildComponent (get names-vector i)])]
[:div
[renderChild 1]
[renderChild 3]
[renderChild 5]]))
I have defined a renderChild function within the let as a helper function, to avoid duplicating the (get names-vector i) every time I use ChildComponent.
Preferably I'd like this to be almost exactly equivalent to:
(defn ChildComponent [text]
[:p text])
(defn ParentComponent [names-vector]
[:div
[ChildComponent (get names-vector 1)]
[ChildComponent (get names-vector 3)]
[ChildComponent (get names-vector 5)]])
where a change in names-vector will potentially trigger a re-rendering of ChildComponents, but not destruction and creation.
Does Reagent expand the first example to the second? Or are there potentially significant performance issues with the first example due to repeated component destruction/creation?

I've been using Reagent for about a year now, and I do exactly this kind of
thing. I'd say it's the suggested thing to do even. Reagent will also
implicitly uses the arguments as indicators to know whether to re-render the
component or not, so it's pretty optimized for knowing when to re-render. Now,
my use case is probably smaller than some others out there, but I've been
extremely happy on the performance front with this kind of setup. As best as I
can tell, it's the encouraged design pattern.
The one thing to watch out for is when dealing with a list of similar
elements (table row, item lists, etc.), you probably want to attach metadata to
uniquely identify each sibling. React will use this under-the-hood to optimize
the rendering.
So, in your case, you may want something more like:
[:div
^{:key 1} [renderChild 1]
^{:key 2} [renderChild 3]
^{:key 5} [renderChild 5]]))
Update
So, I misspoke: the issue with this technique is that a new function is created
every time the parent is re-rendered, which Reagent will see and be forced to
call the new function to get the potentially new children--since the "component"
appears to have changed. I also did not use the functions in the same way the
original poster did. Instead, when I needed to repeat elements, I opted for the
form:
(into []
(for [i [1 3 5]]
[ChildComponent (get names-vector i)]))
Or you could do the following instead:
(let [renderChild (fn [i]
[ChildComponent (get names-vector i)])]
(into []
(for [i [1 3 5]]
(renderChild i))))
The first form simply avoids the extra function definition. The second form
causes renderChild to be evaluated before returning the data, so Reagent never
sees the temporary function--thus side-stepping the need to have Reagent
evaluate it to find out the children haven't changed.
But in looking through the code for our app, we opted for the first form over
the second in all cases, or simply broke the function out and gave it a
name--where it made sense.
Also, this example is poor:
[:div
^{:key 1} [renderChild 1]
^{:key 2} [renderChild 3]
^{:key 5} [renderChild 5]]))
Adding keys only matters if the number of list elements is going to change.
AFAICT, React uses that information to help optimize the diff computation and
allows it to compute more easily which members need to be discarded and which
were added. If it's static, then it doesn't matter. A better example would be:
(into [:div]
(for [val some-coll]
^{:key (compute-key val)} [:p (get some-other-coll val)]))
Where the contents of some-coll can change.

I have explored this issue further, and unfortunately it appears that Reagent does not cache components if the component definition occurs within the let bindings of another component's render function.
Sample code used for experimentation:
(defonce app-state (r/atom 0))
(defn Clicker []
[:h2 {:on-click #(swap! app-state inc)} "Click me"])
(defn Displayer [n]
(let [a (r/atom (js/console.log "Component generated!"))]
(fn [n]
[:p "Hello, yes I ignore my input"])))
(defn Intermediate1 [n]
[Displayer n])
(defn Intermediate2 [n]
(let [Subcomponent (fn [x] [Displayer x])]
[Subcomponent n]))
(defn my-app []
[:div
[Clicker]
[Intermediate2 #app-state]])
(r/render
[my-app]
(js/document.getElementById "app"))
With the Intermediate1 component, the message "Component generated!" is displayed on the console log exactly once when the app is started.
Using Intermediate2, the console log shows "Component generated!" every time the Clicker component is clicked.
Adding ^{:key 1} inside Intermediate2 does not change the results:
(defn Intermediate2 [n]
(let [Subcomponent (fn [x] [Displayer x])]
^{:key 1} [Subcomponent n]))
This shows that the Displayer component is being destroyed and generated every time a new value is being sent to Intermediate2, which will trigger a re-rendering. (Or rather, the rendering of a newly created component)
As such, while the cost may not be prohibitive in practice (particularly for generating a small number of simple components), it appears that binding components in a let inside render functions leads to unnecessary component destruction/creation, potentially negating the efficiency benefits of using a React style virtual DOM.

Please read about this link before asking question.
https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#the-three-ways
A block of data structure or a function return block of data structure will be consider as Form-1. All will be expanded as a single data structure. Any ratom change within this scope will trigger re-render of this single data structure.
Does this cause new child components to be generated every time the render function runs?
Yes. renderChild will be re-created due to the Form-1 nature.
I have defined a renderChild function within the let as a helper function, to avoid duplicating the (get names-vector i) every time I use ChildComponent.
If you want any one of ChildComponent will only be re-render when its own value of (get names-vector i) is changed, but not by the changes in parent or siblings. You should use Form-2, or Form-3. (the names-vector must be a ratom to make reagent to work!)
(defn ChildComponent [names-vector i]
(fn [names-vector i]
[:p (get #names-vector i)]))
(defn ParentComponent [names-vector]
[:div
[ChildComponent names-vector 1]
[ChildComponent names-vector 3]
[ChildComponent names-vector 5]])

Related

Clojurescript/Reagent/Chart.js - Reagent life cycle - chart.update()

We are using chart.js with clojurescript and Reagent. Now I know that Chart.js has a chart.update() method to update the chart with new data. So the question is, how can I setup my component so that it renders the chart on :reagent-render but probably get :component-will-update to call the chart.update() method ?
Basically, is there a way to get a handle on the chart that is created from :reagent-render function in :component-will-update function ?
The usual pattern you follow in this case is to wrap the stateful/mutable object in a React component and use React's lifecycle methods.
The approach is described in detail in re-frame's Using-Stateful-JS-Component but this is how I tend to do it with D3:
(defn graph-render [graph-attrs graph-data]
;; return hiccup for the graph here
(let [width (:width graph-attrs)
height (:height graph-attrs)]
[:svg {:width width :height height}
[:g.graph]]))
(defn graph-component [graph-attrs graph-data]
(r/create-class
{:display-name "graph"
:reagent-render graph-render
:component-did-update (fn [this]
(let [[_ graph-attrs graph-data] (r/argv this)]
(update! graph-attrs graph-data)))
:component-did-mount (fn [this]
(let [[_ graph-attrs graph-data] (r/argv this)]
(init! graph-attrs graph-data)))}))
(defn container []
[:div {:id "graph-container"}
[graph-component
#graph-attrs-ratom
#graph-data-ratom]])
It is important to keep the outer/inner combination because React won't populate its props correctly otherwise.
Another thing to be carefully of is the return of the reagent/argv vector, which, as you can see, contains the props after the first item. I have seen (reagent/props comp) in the wiki page above but I have never tried it myself.

Which changes to clojurescript atoms cause reagent components to re-render?

Consider the following reagent component. It uses a ref function, which updates a local state atom, based on the real size of a span element. This is done in order to re-render the component displaying its own size
(defn show-my-size-comp []
(let [size (r/atom nil)]
(fn []
(.log js/console "log!")
[:div
[:span {:ref (fn [el]
(when el (reset! size (get-real-size el))))}
"Hello, my size is:" ]
[:span (prn-str #size)]])))
If the implementation of get-real-size returns a vector, the log message is printed constantly, meaning the component unnecessarily being re-rendered all the time. If it returns just a number or a string, the log appears only twice - as intended in this scenario.
What's the reason for this? Is it maybe that updating an clojure script atom with a new vector (containing the same values though) internally means putting another JavaScript object there, thus changing the atom? Whereas putting a value produces no observable change? Just speculation...*
Anyways - for the real use case, saving the size of the span in a vector would certainly better.. Are there ways to achieve this?
I cam across this, when trying to enhance the answer given in this question.
* since in JS: ({} === {}) // false
I think I have an answer for why vector behaves differently from string/number. Reagent counts a reagent atom as "changed" (and thus updates a component that depends on it) when identical? returns false between the old and the new values. See the subhead "changed?" in this tutorial:
For ratoms, identical? is used (on the value inside the ratom) to determine if a new value has changed with regard to an old value.
However, it turns out that identical? behaves differently for vectors and for strings/ints. If you fire up either a clj or a cljs repl, you'll see that:
(identical? 1 1)
;; true
(identical? "a" "a")
;; true
(identical? [1] [1])
;; false
(identical? ["a"] ["a"])
;; false
If you look at what identical? does here, you'll see that it tests if its arguments are the same object. I think the underlying internal data representation is such that, in clojure, "a" is always the same object as itself, whereas two vectors containing the same value are not the same object as one another.
Confirmation: with ordinary rather than reagent atoms, we can see that string identity is preserved across atom resets, while vector identity is not.
(def a1 (atom "a"))
(let [aa #a1] (reset! a1 "a") (identical? aa #a1))
;; true
(def a2 (atom ["a"]))
(let [aa #a2] (reset! a2 ["a"]) (identical? aa #a2))
;; false
You can work around the problem with a not= check:
(fn [el]
(when el
(let [s (get-real-size el)]
(when (not= s #size)
(reset! size s)))))
I'm not sure what the reason is for why vectors should differ from other values.
Its rerendering like it is supposed to based on how its written. You are derefing the atom in the same function as you are resetting it. I always keep these separate.
(defn span-size [size]
[:span (prn-str #size)])
(defn show-my-size-comp []
(let [size (r/atom nil)]
(fn []
(.log js/console "log!")
[:div
[:span {:ref (fn [el]
(when el (reset! size (get-real-size el))))}
"Hello, my size is:"]
[span-size]])))

How do I loop through a subscribed collection in re-frame and display the data as a list-item?

Consider the following clojurescript code where the specter, reagent and re-frame frameworks are used, an external React.js grid component is used as a view component.
In db.cls :
(def default-db
{:cats [{:id 0 :data {:text "ROOT" :test 17} :prev nil :par nil}
{:id 1 :data {:text "Objects" :test 27} :prev nil :par 0}
{:id 2 :data {:text "Version" :test 37} :prev nil :par 1}
{:id 3 :data {:text "X1" :test 47} :prev nil :par 2}]})
In subs.cls
(register-sub
:cats
(fn [db]
(reaction
(select [ALL :data] (t/tree-visitor (get #db :cats))))))
result from select:
[{:text "ROOT", :test 17}
{:text "Objects", :test 27}
{:text "Version", :test 37}
{:text "X1", :test 47}]
In views.cls
(defn categorymanager []
(let [cats (re-frame/subscribe [:cats])]
[:> Reactable.Table
{:data (clj->js #cats)}]))
The code above works as expected.
Instead of displaying the data with the react.js component I want to go through each of the maps in the :cats vector and display the :text items in html ul / li.
I started as follows:
(defn categorymanager2 []
(let [cats (re-frame/subscribe [:cats])]
[:div
[:ul
(for [category #cats]
;;--- How to continue here ?? ---
)
))
Expected output:
ROOT
Objects
Version
X1
How do I loop through a subscribed collection in re-frame and display the data as a list-item? ( = question for title ).
First, be clear why you use key...
Supplying a key for each item in a list is useful when that list is quite dynamic - when new list items are being regularly added and removed, especially if that list is long, and the items are being added/removed near the top of the list.
keys can deliver big performance gains, because they allow React to more efficiently redraw these changeable lists. Or, more accurately, it allows React to avoid redrawing items which have the same key as last time, and which haven't changed, and which have simply shuffled up or down.
Second, be clear what you should do if the list is quite static (it does not change all the time) OR if there is no unique value associated with each item...
Don't use :key at all. Instead, use into like this:
(defn categorymanager []
(let [cats (re-frame/subscribe [:cats])]
(fn []
[:div
(into [:ul] (map #(vector :li (:text %)) #cats))])))
Notice what has happened here. The list provided by the map is folded into the [:ul] vector. At the end of it, no list in sight. Just nested vectors.
You only get warnings about missing keys when you embed a list into hiccup. Above there is no embedded list, just vectors.
Third, if your list really is dynamic...
Add a unique key to each item (unique amoung siblings). In the example given, the :text itself is a good enough key (I assume it is unique):
(defn categorymanager []
(let [cats (re-frame/subscribe [:cats])]
(fn []
[:div
[:ul (map #(vector :li {:key (:text %)} (:text %)) #cats)]])))
That map will result in a list which is the 1st parameter to the [:ul]. When Reagent/React sees that list it will want to see keys on each item (remember lists are different to vectors in Reagent hiccup) and will print warnings to console were keys to be missing.
So we need to add a key to each item of the list. In the code above we aren't adding :key via metadata (although you can do it that way if you want), and instead we are supplying the key via the 1st parameter (of the [:li]), which normally also carries style data.
Finally - part 1 DO NOT use map-indexed as is suggested in another answer.
key should be a unique value associated with each item. Attaching some arb integer does nothing useful - well, it does get rid of the warnings in the console, but you should use the into technique above if that's all you want.
Finally - part 2 there is no difference between map and for in this context.
They both result in a list. If that list has keys then no warning. But if keys are missing, then lots of warnings. But how the list was created doesn't come into it.
So, this for version is pretty much the same as the map version. Some may prefer it:
(defn categorymanager []
(let [cats (re-frame/subscribe [:cats])]
(fn []
[:div
[:ul (for [i #cats] [:li {:key (:text i)} (:text i)])]])))
Which can also be written using metadata like this:
(defn categorymanager []
(let [cats (re-frame/subscribe [:cats])]
(fn []
[:div
[:ul (for [i #cats] ^{:key (:text i)}[:li (:text i)])]])))
Finally - part 3
mapv is a problem because of this issue:
https://github.com/Day8/re-frame/wiki/Using-%5Bsquare-brackets%5D-instead-of-%28parentheses%29#appendix-2
Edit: For a much more coherent and technically correct explanation of keys and map, see Mike Thompson's answer!
Here's how I would write it:
(defn categorymanager2 []
(let [cats (re-frame/subscribe [:cats])]
(fn []
[:div
[:ul
(map-indexed (fn [n cat] ;;; !!! See https://stackoverflow.com/a/37186230/500207 !!!
^{:key n}
[:li (:text cat)])
#cats)]])))
(defn main-panel []
[:div
[categorymanager2]])
A few points:
See the re-frame readme's Subscribe section, near the end, which says:
subscriptions can only be used in Form-2 components and the subscription must be in the outer setup function and not in the inner render function. So the following is wrong (compare to the correct version above)…
Therefore, your component was ‘wrong’ because it didn't wrap the renderer inside an inner function. The readme has all the details, but in short, not wrapping a component renderer that depends on a subscription inside an inner function is bad because this causes the component to rerender whenever db changes—not what you want! You want the component to only rerender when the subscription changes.
Edit: seriously, see Mike Thompson's answer. For whatever reason, I prefer using map to create a seq of Hiccup tags. You could use a for loop also, but the critical point is that each [:li] Hiccup vector needs a :key entry in its meta-data, which I add here by using the current category's index in the #cats vector. If you don't have a :key, React will complain in the Dev Console. Note that this key should somehow uniquely tie this element of #cats to this tag: if the cats subscription changes and gets shuffled around, the result might not be what you expect because I just used this very simple key. If you can guarantee that category names will be unique, you can just use the :test value, or the :test value, or something else. The point is, the key must be unique and must uniquely identify this element.
(N.B.: don't try and use mapv to make a vector of Hiccup tags—re-frame hates that. Must be a seq like what map produces.)
I also included an example main-panel to emphasize that
parent components don't need the subscriptions that their children component need, and that
you should call categorymanager2 component with square-brackets instead of as a function with parens (see Using [] instead of ()).
Here's an ul / li example:
(defn phone-component
[phone]
[:li
[:span (:name #phone)]
[:p (:snippet #phone)]])
(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)])))
I grabbed that code from this reframe tutorial.
Also map is preferable to for when using Reagent. There is a technical reason for this, it is just that I don't know what it is.

For a function that updates a world "state", I want to return a vector of strings of events that happened

I have this small game world state, something like the following:
(defn odds [percentage]
(< (rand-int 100) percentage))
(defn world []
{:entities []})
(defn make-bird []
{:pos [(rand-int 100) (rand-int 100)]
:age 0
:dir (vec/dir (rand (. Math PI)))})
(defn generate-entities [entities]
(if (odds 10)
(conj entities (make-bird))
entities))
(defn update-entity [entity]
(-> entity
(update :pos (partial vec/add (:dir entity)))
(update :age inc)))
(defn update-entities [entities]
(vec (map update-entity entities)))
(defn old? [{age :age}]
(> age 10))
(defn prune-entities [entities]
(vec (filter #(not (old? %)) entities)))
(defn update-world [world]
(-> world
(update :entities generate-entities)
(update :entities update-entities)
(update :entities prune-entities)))
So update-world goes through three steps. First there's a 1/10 chance of generating a new bird entity, which flies in a random direction. Then it updates all birds, updating their position and incrementing their age. Then it prunes all old birds.
I use this same technique for generating particles systems. You can do fun stuff like (iterate update-world (world)) to get a lazy list of world states which you can consume at whatever frame rate you want.
However, I now have a game world with autonomous entities which roam around and do stuff, kind of like the birds. But I want to get a textual representation of what happened when evaluating update-world. For example, update-world would ideally return a tuple of the new world state and a vector of strings - ["A bird was born at [12, 8].", "A bird died of old age at [1, 2]."].
But then I really can't use (iterate update-world (world)) anymore. I can't really see how to do this.
Is this something you'd use with-out-string for?
If you want to enhance only your top-level function (update-world) in your case you can just create a wrapper function that you can use in iterate. A simple example:
(defn increment [n]
(inc n))
(defn logging-increment [[_ n]]
(let [new-n (increment n)]
[(format "Old: %s New: %s" n new-n) new-n]))
(take 3 (iterate logging-increment [nil 0]))
;; => ([nil 0] ["Old: 0 New: 1" 1] ["Old: 1 New: 2" 2])
In case you want to do it while collecting data at multiple level and you don't want to modify the signatures of your existing functions (e.g. you want to use it only for debugging), then using dynamic scope seems like a reasonable option.
Alternatively you can consider using some tracing tools, like clojure/tools.trace. You could turn on and off logging of your function calls by simply changing defn to deftrace or using trace-ns or trace-vars.
There are two potential issues with using with-out-str
It returns a string, not a vector. If you need to use a vector, you'll need to use something else.
Only the string is returned. If you are using with-out-str to wrap a side-effect (e.g., swap!), this might be fine.
For debugging purposes, I usually just use println. You can use with-out if you want control over where the output goes. You could even implement a custom stream that collects the output into a vector of strings if you wanted. You could get similar results with a dynamically bound vector that you accumulate (via set!) the output string (or wrap the vector in an atom and use swap!).
If the accumulated vector is part of the computation per se, and you want to remain pure, you might consider using a monad.
What about using clojure.data/diff to generate the string representation of changes? You could do something like this:
(defn update-world [[world mutations]]
(let [new-world (-> world
(update :entities generate-entities)
(update :entities update-entities)
(update :entities prune-entities))]
[new-world (mutations (clojure.data/diff world new-world))]))
Then you could do something like (iterate update-world [(world) []]) to get the ball rolling.

Why are multi-methods not working as functions for Reagent/Re-frame?

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.
So, a component like this: [pages #some-ratom]
will rerender when pages changes or #some-ratom changes.
From reagent's point of view, pages hasn't changed since last time, it is still the same multi-method it was before. But #some-ratom might change, so that could trigger a rerender.
But when this rerender happens it will be done using a cached version of pages. After all, it does not appear to reagent that pages has changed. It is still the same multimethod it was before.
The cached version of pages will, of course, be the first version of pages which was rendered - the first version of the mutlimethod and not the new version we expect to see used.
Reagent does this caching because it must handle Form-2 functions. It has to keep the returned render function.
Bottom line: because of the caching, multimethods won't work very well, unless you find a way to completely blow up the component and start again, which is what the currently-top-voted approach does:
^{:key #current-route} [pages #current-route]
Of course, blowing up the component and starting again might have its own unwelcome implications (depending on what local state is held in that component).
Vaguely Related Background:
https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#appendix-a---lifting-the-lid-slightly
https://github.com/Day8/re-frame/wiki/When-do-components-update%3F
I don't have all the details, but apparently, when I was rendering pages like this:
[:main.container
[alerts/view]
[pages #current-route]]
Reagent was failing to notice that pages depended on the value of #current-route. The Chrome React plugin helped me figure it out. I tried using a ratom instead of a subscription and that seemed to work fine. Thankfully, telling Reagent/React the key to an element is easy enough:
[:main.container
[alerts/view]
^{:key #current-route} [pages #current-route]]
That works just fine.
The first problem that jumps out at me is that your methods take no arguments:
(defmethod layout/pages :register [] [register-page])
^ arglist
Here you have an empty arglist, but presumably you'll be calling this multimethod with one or two arguments (since its dispatch function is a keyword and keywords can be called with one or two arguments).
If you want to call this multimethod with a single argument and just ignore it inside the body of the :register method, change the above to
(defmethod layout/pages :register [_] [register-page])
^ argument to be ignored
Also, I expect you'll probably want to call pages yourself like you previously did (that is, revert the change to square brackets that you mentioned in the question).
This may or may not fix the app – there may be other problems – but it should get you started. (The multimethod will definitely not work with those empty arglists if you pass in any arguments.)
How about if you instead have a wrapper pages-component function which is a regular function that can be cached by reagent. It would look like this:
(defn pages-component [state]
(layout/pages #state))