Nested reagent atoms not redrawing

I am stuck with one issue I’ve fallen into. I have the following code:

(ns test.main
  (:require [reagent.core :as r]))

(def +a+ (r/atom nil))
(def +b+ (r/atom 0))

(defn page []
  [:div [:div (str "Counter is " @+b+)]
   [:div [:input {:type "button"
                  :value "inc"
                  :on-click #(swap! +b+ inc)}]]])

(defn home-page []
  [:div [:div [:input {:type "button"
                       :value "Show counter"
                       :on-click #(swap! +a+ page)}]]

After compilation, page starts with one Button called Show counter. On click it show counter with another button. But here comes the problem - by clicking on inc button counter is not increasing - component is not redrawing. Clicking on Show counter again updates the value, but it seems like react is not picking state changes in nested reagent atoms.

How this should be done?

I’m having a hard time seeing what you’re trying to achieve, could you describe it more clearly? Your on-click handler in home-page swap!s the +a+ atom with the page function – that won’t work at all. The swap! function takes a function as its second argument which is supposed to update the value of the atom (see!).

If you’re trying to display the page component, you’re going to have to do something like this:

(defn home-page []
   [:input {:type "button"
            :value "Show counter"
            :on-click #(swap! +a+ inc-or-something)}]
1 Like

By providing minmal example I’ve probably discarded idea behind it…

That’s more complete example:

(ns test.main
  (:require [ :as menu]
            [test.about :as about]
            [test.counter :as counter]
            [test.list :as listing]
            ["react-bootstrap" :as bs]
            [reagent.core :as r]))

(def +content+ (r/atom ""))

(def alert (r/adapt-react-class (aget bs "Alert")))
(def button (r/adapt-react-class (aget bs "Button")))
(def NavItem (r/adapt-react-class (aget bs "NavItem")))

(defn home-page []
  [:div [:h2 "Welcome to reagent-test"]
   [:> (aget bs "Navbar")
    [:> (aget bs "Navbar" "Header")
     [:> (aget bs "Navbar" "Brand")
    [:> (aget bs "Nav")
     [NavItem {:event-key 1 :href "#" :on-click #(swap! +content+ about/page)} "About"]
     [NavItem {:event-key 1 :href "#" :on-click #(swap! +content+ counter/page)} "Counter"]]]

(ns test.counter
  (:require [reagent.core :as r]))

(def +counter+ (r/atom 0))

(defn page []
  [:div [:h2 "Counter for reagent-test"]
   [:div (str "Counter is " @+counter+)]
   [:div [:input {:type "button"
                  :value "increase counter!"
                  :on-click #(swap! +counter+ inc)}]]])

In main I am able to switch between displayed pages by altering +content+ atom. On counter/page there is another atom +counter+.

What’s happening here is that during the event handlers, swap! evaluates counter/page and then places that value in the +content+ atom. This isn’t going to work, because the counter will be evaluated during the on-click event handler, not during rendering. That’s why you don’t get a re-render.

I think a minimal way to fix this is to change your navigation handlers to simply store the hiccup vector in your content atom rather than evaluating the render functions. So do something like this: #(reset! +content+ [counter/page]). This will actually create a component instead of evaluating the render function out-of-band. It will also work with form-2 components, which your current technique will not do.

Let me know if that makes sense to you.

One other comment that isn’t related to your problem: Don’t use aget on objects. We’re only supposed to use aget on array references with numeric indices. Either use cljs-oops or, if you are using shadow-cljs (it looks like you may be), just refer the symbols in directly: (:require ["react-bootstrap" :refer [Alert Button NavItem]]). Shadow-cljs can infer the externs quite reliably so you don’t need to mess around with string references.

This makes perfect sense. Thank you!

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.