Render two components in reagent / reframe - clojurescript

I have some code:
(defn second-panel []
[:div
[:h1 "Hello "]
])
(defn root-container []
(second-panel)
(let [name (re-frame/subscribe [::subs/name])]
[:div
[:h1 "Hello from " #name]
]))
but only the root-container component renders. Why is my second-panel function not rendering?

You have to add (second-panel) to your div. The return value of (second-panel) is currently ignored.
(defn root-container []
(let [name (re-frame/subscribe [::subs/name])]
[:div
(second-panel)
[:h1 "Hello from " #name]
]))

The correct solution to returning multiple virtual DOM elements from a function without wrapping them in a container element is to use a Fragment. In reagent this is handled by the :<> special keyword.
(defn second-panel []
[:div
[:h1 "Hello "]])
(defn root-container []
(let [name (re-frame/subscribe [::subs/name])]
[:<>
[second-panel]
[:div
[:h1 "Hello from " #name]
]]))
;; or, with the nested let. both variants are fine.
(defn root-container []
[:<>
[second-panel]
(let [name (re-frame/subscribe [::subs/name])]
[:div
[:h1 "Hello from " #name]
])])
There is also a different in (second-panel) and [second-panel] since (second-panel) will actually call the function directly which means it will not behave like a regular reagent function but instead become part of the caller. You should prefer the [second-panel] notation for all "component" type functions.

Related

form-3 component not rerendering anything even though :component-did-update is called

I have the following code to test out form-3 components:
(defn inner [data]
(reagent/create-class
{:display-name "Counter"
:component-did-mount (fn []
(js/console.log "Initialized")
[:h1 "Initialized! " data])
:component-did-update (fn [this _]
(let [[_ data] (reagent/argv this)]
(js/console.log (str "Updated " data))
[:div (str "My clicks " data)]))
:reagent-render (fn [] [:div (str "My clicks " data)])}))
I am able to successfully trigger both the :component-did-mount and the :component-did-update as there is the expected output in the console log. However, neither of the two functions actually change anything on the page. It just shows the initial state of [:div (str "My clicks " data)] the whole time.
What am I doing wrong? Ps. I have read the reagent docs and the purelyfunctional guide.
You have to repeat the parameters in the :reagent-render function:
(fn [data] [:div (str "My clicks " data)])

Routing using default template in reagent

I am trying to use reagent to build my very basic project but I have a problem with routing and its parameter. This is from reagent looks like
EDITED - :require s added
(ns hammerslider.core
(:require [reagent.core :as reagent :refer [atom]]
[secretary.core :as secretary :include-macros true]
[accountant.core :as accountant]))
;; Views
(defn home-page []
[:div [:h2 "Welcome to hammerslider"]
[:div [:a {:href "/c/12"} "go custom"]]])
(defn c [test]
[:div [:h2 (str "on C " test)]
[:div [:a {:href "/"} "go to the home page"]]])
I am trying to get 12 from c route which is the route handling is look like this
(def page (atom #'home-page))
(defn current-page []
[:div [#page]])
(secretary/defroute "/" []
(reset! page #'home-page))
(secretary/defroute "/c/:test" [test]
(reset! page #'c)
I'm trying to catch the test parameter with the view function but it appears on C, not on C 12. How do I get to transfer the test parameter in to the view of c? or should I save it on different atoms?
EDITED - Mine solved by saving parameters into atom and it works, but is it the right way to pass the parameter?
(def parameter (atom ()))
(defn c []
[:div [:h2 (str "on C " (:test #parameter))]
[:div [:a {:href "/"} "go to the home page"]]])
(secretary/defroute "/c/:test" {:as params}
(do (js/console.log params)
(reset! parameter params)
(reset! page #'c)
))
It is depended on how you use your route parameters. The only guarantee between your program and reagent is if the value in ratom changed, the reagent component will be changed accordingly.
The TodoMVC is quite feature completed example for you to use reagent and secretary.
https://github.com/tastejs/todomvc/blob/gh-pages/examples/reagent/src/cljs/todomvc/routes.cljs
By the way, most of the time I will use re-frame instead of using reagent directly.

Reagent state not updating after setInterval

I have this reagent component that uses setInterval to change its state:
(defn foo []
(let [value (atom 1)]
(js/setInterval (fn [] (reset! value (rand-int 100)) (println #value)) 1000)
(fn []
[:p #value])))
I can see the value being printed, a different one each time, but the html doesn't change. Why is that?
And the answer is that I should have been using a reagent.core/atom instead of an atom.

Let Sub Component React on Parent's State Plus Having its Own State

Consider the following Reagent components:
(defn sub-compo [n]
(let [state (r/atom {:colors (cycle [:red :green])})]
(fn []
[:div {:style {:color (-> #state :colors first)}
:on-mouse-move #(swap! state update :colors rest)}
"a very colorful representation of our number " n "."])))
(defn compo []
(let [state (r/atom {:n 0})]
(fn []
[:div {:on-click #(swap! state update :n inc)}
"Number is " (#state :n) "."
[sub-compo (#state :n)]])))
I tried to make up an example, in which a sub component should depend on the state of its parent component. However the sub component should have an internal state as well. The above does not work properly. When the state in compo changes sub-compo is not initialized a new.
Which would be the way to go here, in order to let sub-compo be in sync with comp? Whenever the state of comp changes sub-comp should actually be initialized anew, meaning it's color state is set to the initial value again.
Here's a solution that does at least what I want. It uses a cursor and a watch. But maybe there is a simpler way to do so, anyways:
(defn sub-compo [n]
(let [init-state {:colors (cycle [:red :green])}
state (r/atom init-state)]
(add-watch n :my (fn []
(reset! state init-state)))
(fn []
[:div {:style {:color (-> #state :colors first)}
:on-mouse-move #(swap! state update :colors rest)}
"a very colorful representation of our number " #n "."])))
(defn compo []
(let [state (r/atom {:n 0})]
(fn []
[:div {:on-click #(swap! state update :n inc)}
"Number is " (#state :n) "."
[sub-compo (r/cursor state [:n])]])))
The above does not work properly. When the state in compo changes
sub-compo is not initialized a new.
This is because the inner function of sub-compo needs to receive the argument n as well.
Whenever the state of comp changes sub-comp should actually be
initialized anew, meaning it's color state is set to the initial value
again.
You could use :component-will-receive-props for this.
This worked for me:
(defn sub-compo [n]
(let [init {:colors (cycle [:red :green])}
state (r/atom init)]
(r/create-class
{:component-will-receive-props
(fn [this [_ n]]
(reset! state init))
:reagent-render
(fn [n]
[:div {:style {:color (-> #state :colors first)}
:on-mouse-move #(swap! state update :colors rest)}
"a very colorful representation of our number " n "."])})))

How to override onload methods in ClojureScript?

I am trying to override onload function of document and Image in ClojureScript. I think that set! should be possible to do it, but i am not getting any success. Relevant code is as follows :
(defn load-image [img-path]
(let [img (js/Image.)]
(do (set! (.-src img) img-path)
img)))
(defn add-img-canvas [img-path width height]
(let [img (load-image img-path)]
(set! (.-onload img)
(fn [] ;; This function is never called.
(let [canvas (get-scaled-canvas img width height)]
(do (pr-str canvas)
(swap! game-state :canvas canvas)))))))
(defn hello-world []
(let [count (atom 1)]
(fn []
[:div
[:h1 (:text #game-state)]
[:div (do (swap! count inc) (str "count is " #count))]
[:canvas (:canvas #game-state)]])))
(reagent/render-component [hello-world]
(. js/document (getElementById "app")))
(set! (.-onload js/document)
(fn [] ;; This function is also never called.
(add-img-canvas (:img-src game-state) 100 130)))
;;(. js/document onload)
Anonymous functions in add-img-canvas is not getting called. What am i doing wrong ?
I think it may be down to the difference between document.onload vs window.onload. The latter does work as expected.
See this for more details between the two.