Using the REPL with reagent


#1

I’m working through some reagent tutorials and would like to learn to use the repl as I go. The reagent components have this form

(defn counting-button [txt]
  (let [state (reagent/atom 0)] ;; state is accessible in the render function
    (fn [txt]
      [:button.green
        {:on-click #(swap! state inc)}
        (str txt " " @state)])))

In the repl, how can I see what state is set to? counting-button is a function, so how can I get at the data inside of it?


#2

For development purposes you could move the state out of the function, so you can access it at the repl:

(defonce state (reagent/atom 0))

(defn counting-button [txt]
  (fn [txt]
    [:button.green
     {:on-click #(swap! state inc)}
     (str txt " " @state)]))

At the repl make sure you’re in the same namespace where the state atom is defined, and type @state to check it’s current value.

You may also use add-watch to monitor the state changes of your atoms, like so:

(defn counting-button [txt]
  (let [state (reagent/atom 0)]

    (add-watch state :counting-state
               (fn [_ _ _ new-state]
                 (println "new counting state:" new-state)))
    (fn [txt]
      [:button.green
       {:on-click #(swap! state inc)}
       (str txt " " @state)])))

You may also use Devcards to develop and test your component before adding it to your project, like this.


#3

It might also help (while you troubleshoot) to use …(pr-str @state) … instead of just @state inside the button text, to produce a readable form of it, e.g., “” instead of nothingness. Furthermore, there is no dishonor in (print (pr-str @state)) as it helps you know not only what’s in the variable but also how often, and when, the function body runs.


#4

Good to know about pr-str, I hadn’t come across that yet. I’m well versed in printf debugging. I haven’t used an actual debugger in over a decade. Working with multi-threaded and multi-process programs, debuggers just don’t work as well. Trace logs are much easier to use.

I keep hearing how awesome an experience the REPL is, so I’m looking forward to experiencing said awesomeness.

It struck me as strange how the reagent code was structured since it seems like trying to force OOP onto an FP language. It seems to me the FP way would be to have the state passed in so that, for example, the description of a button could be used to initialize different types of buttons. I’m thinking I’ll probably end up doing it that way instead of the nested let approach.


#5

The nested let approach doesn’t stop you from doing that. You could have sth like:

(defn counting-button [{:keys [initial-state]}]
  (let [local-state (reagent/atom (or initial-state 0))]
    (fn [{:keys [txt btn-class]}]
      [:button {:class (str btn-class)
                :on-click #(swap! local-state inc)}
       (str txt " " @local-state)])))

An example where this could be useful is when you have a dropdown, and want the component to keep track if it’s open or not.

Or you could write it the FP way:

(defn counting-button [{:keys [value txt btn-class on-click]}]
  [:button {:class (str btn-class)
            :on-click on-click}
   (str txt " " value)])


(def state (reagent/atom 0))

[counting-button {:value @state
                    :on-click #(swap! state inc)
                    :txt "Button text"
                    :btn-class "green"}]

Both approaches – components which keep their own state vs. take the state as an argument, both have their pros and cons… Here are some videos I found helpful in this regard (they use re-frame for state management, but it’s pretty similar to using an atom):