Why need to return a function in Reagent component? - clojurescript

From the Reagent introduction, a simple timer component:
(defn timer-component []
(let [seconds-elapsed (r/atom 0)]
(fn []
(js/setTimeout #(swap! seconds-elapsed inc) 1000)
[:div
"Seconds Elapsed: " #seconds-elapsed])))
and below it reads
The previous example also uses another feature of Reagent: a component
function can return another function, that is used to do the actual
rendering. This function is called with the same arguments as the
first one.
This allows you to perform some setup of newly created components
without resorting to React’s lifecycle events.
Can someone remind me of the underlying principle here? Why do we need this anonymous function? Why not just
(defn timer-component []
(let [seconds-elapsed (r/atom 0)]
(js/setTimeout #(swap! seconds-elapsed inc) 1000)
[:div
"Seconds Elapsed: " #seconds-elapsed])))

From what I remember, Reagent calls timer-component every time it wants to render - potentially setting up the same piece of state (seconds-elapsed) over and over again.
By returning that anonymous function instead, it tells Reagent "use this to render timer-component". This way your state setup is separated from rendering, and like your doco quote says, its a way to perform state setup without using Reacts lifecycle events.
Hope that makes sense

Tl;dr: The anonymous function that is returned is the render method, which every component must have. You can elide the anonymous function if you use the with-let macro in Reagent.
The indispensable part of a React component is a render function, which takes a single object argument and returns a React element. The difference between render
and the component constructor is that, while both methods are called upon construction, render is called on each update. (For instance, if someone calls the setState method of the component).
In the above example, the difference between the inner, anonymous function and the outer timer-component function is the same as between render and the constructor. Notice that the anonymous function closes over the variables bound in the let clause, which allows it to be stateful. If timer-component itself were the render function, then it would be called on every update, and seconds-elapsed would be endlessly reset to zero.
See the doc on the Reagent repo called "Creating Reagent Components".

Related

Recursive Call with goog.dom.animationFrame

I am trying to use google closure animationFrame feature.
I would like to create an animation task with it and to call that created task recursively.
I defined a def named animationTask.
When I try to use that def recursively in that task it fails. It logs out that animationTask is undefined and thus can not be used as a function.
Could anyone point me in the right direction please?
I feel like I am missing some basic clojure knowledge here.
Your code is calling the animation task function before it is defined. It is analogous to this simpler code:
(defn create [x] (fn []))
(def task (create {:measure (task)}))
If you try that in a REPL, you'll see that task is being called while it is still undefined.
Instead, the value under :measure is supposed to be a function, and the API takes a JavaScript object. This would be analogous to revising the above example to be:
(def task (create #js {:measure (fn [state] (task))}))

Calling a function from another namespace in ClojureScript

I'm a newbie with CojureScript because I got the LISP itch some months ago and then I migrated the API to Clojure and I'm loving it. Now I want to migrate the frontend too but this is my first week with CLJS.
So, I have this function:
(defn foo []
(events/listen (gdom/getElement "icon-add") EventType.CLICK
(fn [e] (.log js/console (str ">>> VALUE >>>>> " e)))))
the function works great in the core.cljs file, but if I move it to users.cljs file and I call it from the core namespace with:
(ns zentaur.core
(:require [zentaur.users :as users]
(users/foo)
the DOM element "icon-add" is never found and instead I get the error message:
Uncaught goog.asserts.AssertionError {message: "Assertion failed: Listener can not be null.", reportErrorToServer: true,
in the browser console. If I move the function back to core.cljs, all works fine again. Then my question is: how can I move a function to another NS and be sure it keeps working?
UPDATE:
I noted that if I call the listener directly in users.cljs:
(events/listen (gdom/getElement "icon-add") EventType.CLICK
(fn [] (.log js/console (str ">>> events/listen in users ns"))))
(I mean out of any function), all works fine, the code find the DOM element.
SECOND UPDATE
Thanks a lot for your answers but this issue looks like a "compiling time" problem. All the listeners:
(events/listen (gdom/getElement "icon-add") EventType.CLICK foo-funct)
must be loaded when CLJS runs at first time. Loading another ns is a "second time" thing and then the DOM is not reachable anymore.
In the core namespace you need to require the 2nd namespace:
(ns xyz.core
(:require [xyz.users :as users] ))
(users/foo) ; call the function
This assumes your code is laid out as
src
src/xyz
src/xyz/core.cljs
src/xyz/users.cljs
There are also some good ideas here on various tradeoffs in naming and references to other namespaces.
The user namespace is special in that it is designed to be pre-loaded, and for development only. It is also the only time you will ever see a namespace that does not have a parent package. I say this just to warn you off using user as a namespace name - doing so will just cause confusion - whether it has a parent package or not.
Your problem seems to be that one of the arguments to events/listen is somehow returning nil. Do you have gdom as a require in zentaur.users, so: [goog.dom :as gdom]?
Google Closure library throws the assertion error because your listener function, i.e., the third parameter to listen is null.
Looking at the source code for the library, it tries to wrap the third parameter into a Listener, and the error string is produced from this failed assertion for a truthy value.
I often have similar problem, when I accidentally put extra parenthesis around the function. In effect, this leads to the function being called immediately after its declaration. This may happen without warning, even when your declared function requires one or more parameters; ClojureScript runs on JS, which does not check the arity when calling the function. Since the body of your listener is just console.log, which returns null, you would be trying to assign the null as listener.
You did note that the code example that you give is working in core.cljs. Indeed, the example does not show any obvious reason why the function is null in this case. Perhaps there was a small error in copy-pasting this function to users.cljs, e.g., extra parenthesis added?

Writing re-frame events that do not change app-db

There are certain events that do not result in app-db changing. They only change the dom, e.g: init a custom scroll, getting the selected text, etc. How should I deal with them in re-frame, since the event handler requires to return a new app-db? I am getting around by returning the existing db, but it does not seem right. Is there a better way to do it? A few of my handlers look like this:
(re-frame/reg-event-db
:init-link-viewer
(fn [db [_ highlights]]
(utils/load-highlights highlights)
(utils/init-selection)
db))
You can use the reg-event-fx function to register an effect handler which returns an effects map (as opposed to reg-event-db which only returns db). Your effects map can be empty, and doesn't need to return a db. See Effects for more info on this.
You could rewrite your event as:
(reg-event-fx
:init-link-viewer
(fn [db [_ highlights]]
(utils/load-highlights highlights)
(utils/init-selection)
{}))
However you may want to take this further, and return your side effects as data. This means that your event handlers are easily testable, and decouples the event from it's side effects. This will mean that you need to write and register effects handlers as well. This could look something like:
(reg-event-fx
:init-link-viewer
(fn [db [_ highlights]]
{:load-highlights highlights
:init-selection true}))

How to call re-frame.core/dispatch and re-frame.core/subscribe in same event handler

For example:
(defn starrating []
(reagent/create-class
{:reagent-render
(fn []
[:div
[:input {:type "checkbox"
:on-click #(do (re-frame/dispatch
[:set-star-rating
(-> % .-target .-checked)])
(get-data-from-server))}]])}))
(defn get-data-from-server []
(let [star (re-frame/subscribe [:star-rating])]
(ajax/GET (str "http://192.168.0.117:8080/json/searchhotels.json"
"?star=" #star)
{:response-format :json
:keywords? true
:handler success-handler
:error-handler error-handler})))
In the above example the checkbox is not set.
When the checkbox is ticked, the star variable is set to true
But after this, when we call subscribe to get the value in star it is returning previous value i.e false
It will call: http://192.168.0.117:8080/json/searchhotels.json?star=false
When you un-check the checkbox, the request becomes
http://192.168.0.117:8080/json/searchhotels.json?star=true
Why re-frame.core/subscribe is returning previous set value?
re-frame has a data cycle: db -> subscriptions -> view -> dispatch events -> db. That's the most important thing to understand here.
Try setting your checkbox value in starrating with a subscription from app-db, so that the data flows from app-db into your view.
Also try putting get-data-from-server inside an event handler, so that your view is not handling all of the mechanics of querying, but rather is just dispatching events, without the knowledge of what needs to happen to respond to them.
There's a bunch of good documentation on this at https://github.com/Day8/re-frame/tree/master/docs
Subscriptions are reactions meant to be use with reagent components.
Dispatch is asynchronous. Use dispatch-sync if you want it synchronous.
Look into https://github.com/Day8/re-frame-http-fx to make ajax calls while keeping your event handlers as pure functions.
Read the docs, re-frame has very good documentation. Readme.md in github. Have a look at example apps in the repository for examples.

om get-props vs get-state

I am trying to grasp the purpose of the two OM functions get-state and get-props. Have a look at the following example:
(defn example [app owner]
(reify
om/IInitState
(render-state [this state]
(println "app-state: " app )
(println "state: " state )
(println "get-props: " (om/get-props owner) )
(println "get-state: " (om/get-state owner) )
(dom/div nil "hello"))))
You will notice that app and state contain exactly what get-props and get-state return, which seems at the first glance pretty redundant.
Now, not all the lifecycle functions (e.g. IWillMount ) pass the state argument, so when you need it in such circumstances, it's obvious that you need to invoke om/get-state to get access to it.
However, for the app-state it looks different to me. You always have the app-state cursor available in all functions since it's a top-level argument of the function, even if you need it in callbacks you can just pass it around. Most examples/tutorials make use of get-state but I can't find an example of get-props. Is get-props redundant? Where would I use it?
And one more thing related to this construct. In React we have props and state, but in OM we have app-state and state (internal state) which confused me when learning OM. Props are passed from parent to child in React, likewise in OM we pass app-state (cursors) to children. Are the following observations valid?
app-state is the OM equivalent of React' props
props is React is just data, while app-state in OM is data wrapped in cursors
this means that OM doesn't have props, only app-state cursors, hence the function get-props really means get-app-state
According to the docs, get-props is mostly (or exclusively) needed in the IWillReceiveProps phase. will-receive-props gets a next-props argument, which contains the future app-state/props. get-props gives you the current app-state/props so that you can compare the two.
From the Om docs for IWillReceiveProps:
In your implementation if you wish to detect prop transitions you
must use om.core/get-props to get the previous props. This is
because your component constructor function is called with the
updated props.
So the rest of the time, get-props isn't necessary because, as mentioned in the question, you have access to the cursor.