Can i use (if) as a form-2 reagent render-function - clojurescript

I use an (if) condition as form-2 render-function in this way:
(defn bro [dex]
(let [yo (inc dex)]
(if true
[:div (str yo)])))
instead of this way:
(defn bro [dex]
(let [yo (inc dex)]
(fn [dex]
(if true
[:div (str yo)]))))
Is a problem if i use an (if) statement instead of an (fn) function?
And what happens when the statement gone false? The render function returns with nil?

No, this won't work as you expect. To understand the difference, consider the following two components:
(defn test-component-if []
(let [a (atom 1)
_ (.log js/console "let in -if")]
(if (odd? #a)
[:div
[:p "odd"]
[:button {:on-click #(swap! a inc)}
"inc"]]
[:div
[:p "even"]
[:button {:on-click #(swap! a inc)}
"inc"]])))
(defn test-component-fn []
(let [a (atom 1)
_ (.log js/console "let in -fn")]
;; I dub thee test-inner
(fn []
(if (odd? #a)
[:div
[:p "odd"]
[:button {:on-click #(swap! a inc)}
"inc"]]
[:div
[:p "even"]
[:button {:on-click #(swap! a inc)}
"inc"]]))))
test-component-fn works as expected, while test-component-if does not. Why is that? Well when a reagent component can return one of two things (I'm ignoring "type-3" components, as that hooks into react knowledge). It can return
a vector
another function
If it returns a vector, the function itself becomes the render function, in our case test-component-if. When it returns a function, the function that was returned, not the original function, is the render function. In this case, what I have dubbed test-inner
When Reagent calls a render function, it tracks the atoms that function accesses, and whenever that atom changes it calls the render function. So what happens when we use test-component-if?
Reagent calls test-component-if
Our let clause binds a new atom a to 1.
A vector is returned
We click the button
The atom a is incremented
Reagent sees the change to a and calls test-component-if
Our let clause binds a new atom a to 1. (A different atom than our previous a)
Ooops!
So a is always 1. You can verify this by looking at the console, and seeing that the message is printed every time you click the button.
Now how about test-component-fn?
Reagent calls test-component-fn
Our let clause binds a new atom a to 1.
test-component-fn returns test-inner which closes over a
Reagent calls test-inner
We click the button
a is incremented
Reagent sees the change to a and calls test-inner
Repeat as many times as you want.
You can verify that let only gets executed once again on the console. Click the button as many times as you want, the message will only be printed when it's first rendered.
In terms of an if without an else clause, this will indeed return nil. It's convention to use when instead of if in such cases, which makes it clear the lack of an else is intended. It also has the advantage of including an implicit do. As for what Reagent will do when it encounters that nil, in most cases it will silently remove it and display nothing.

Is a problem if i use an (if) statement instead of an (fn) function?
I think you meant to use an if inside an fn function, but either way it's not a problem.
And what happens when the statement gone false? The render function returns with nil?
Reagent handles these gracefully, a nil will be skipped (no corresponding child is created). If you see the TODOs app example in the official docs, you'll see source has code like the following:
(when (pos? done)
[:button#clear-completed {:on-click clear-done}
"Clear completed " done])]))
In this case, if done is not a positive number, the return value of this expression is nil and the button to clear the completed tasks is simply not added to the DOM.

Related

Using an anonymous Js function in a go block

I'm attempting to do some timed animation in clojurescript/reagent and I'm trying to use core.async to achieve series of timed steps in order. I'm using a third party js react library, so, to call its functions I'm using the form (fn [] ^js (.function (.something #ref)). However putting this anonymous function inside the go block as follows doesn't work -
(go
(js/console.log "going")
(<! (timeout 3000))
(fn [] ^js (.function (.somedata #lib))
(js/console.log "landed")
)
This returns "going" and "landed" timed correctly and works when putting another console.log function in its place. However if you wrap this console.log in an Fn it no longer gets called. It is being called in the :on-click handler of a component. What have I missed?
That ^js there is unnecessary.
When you wrap something in (fn [] ...), that something is not called unless that fn is called. I.e. ((fn [] ...)). But then, you end up creating a function and calling it immediately, which is completely unnecessary.
So, assuming I understand you correctly, you can simply replace that (fn [] ^js ...) with (-> #lib .somedata .function).
On a side note, somedata sounds like it's just a field that has some data and not a function that needs to be called. If that's the case, use .-somedata instead of .somedata.
As mentioned by Eugene Pakhomov using (fn [] ...) only creates a function, it does not call it. Thus it it basically just elimnated entirely without doing anything.
Your motiviation here seems to get rid of the inference warning. So the underlying problem is that core.async is rather forgetful when in comes to type hints. If you ask me you shouldn't do any interop in go blocks at all and rather move it all out. Either via defn or local function outside the go.
(defn do-something []
(.function (.somedata ^js #lib)))
(go
(js/console.log "going")
(<! (timeout 3000))
(do-something)
(js/console.log "landed"))
(let [do-something (fn [] (.function (.somedata ^js #lib)))]
(go
(js/console.log "going")
(<! (timeout 3000))
(do-something)
(js/console.log "landed")))
Also, just a word of caution. Using core.async for this will substantially increase the amount of code generated for code in go blocks. If you really need to do is delay something use (js/setTimeout do-something 3000). Use go with caution, it'll easily generate 10x the code for some things than would normally be required.

Subscribe and add-watch in uberjar

I have component created with reagent/create-class which gets atom created by subscribe. I am adding a watch on :component-did-mount in order to call component (js) function on request, which is triggered by change in the atom (there is a server round trip). It looks somewhat as following:
(defn editor [text issue-hints]
(let []
(reagent-core/create-class
{:component-did-mount
#(let [editor (js/SimpleMDE.
(clj->js {...}))]
(do
...
(add-watch issue-hints :watch-issue-hints (show-hint (-> editor .-codemirror)))
...))
:reagent-render
(fn [this] [:textarea])})))
(defn edit-panel [text]
(let [test (re-frame.core/subscribe [:issue-hints])]
[box
:class "issue-detail"
:size "auto"
:child [:div.issue-detail [editor text test]]]))
It works well when debugging the project, but once uberjar file is run, watch handler never gets called. What is the most strange thing to me is that if at least dummy reference to subscription atom is added, it works well again (eg. dummy #issue-hints in same let as subscription). Server round trip looks good.
Can someone give me explanation and/or suggestion for more reasonable fix/workaround?
It looks like you need two params in :reagent-render, not one -
:reagent-render
(fn [text issue-hints] [:textarea])}))) ---> Not "this", but should match initial args
When you only pass one arg, and it is not derefed in the component-did-mount fn, it won't receive the subscription on subsequent changes.
Further, I don't think that you need to explicitly use add-watch, as that is what the re-frame subscription is giving you ootb. By using the deref syntax #issue-hints, the element will be notified any time a change happens to issue-hints, and you should be able to watch the state from the app-db.
If you add the 2nd arg, my guess is that you could drop the add-watch and it should work as expected.
Here are the docs and if you look at the code sample, you see the repetition of the args...
----- Edit: Will Form-2 work? -----
(defn editor [text issue-hints]
(let [hints #issue-hints
editor (js/SimpleMDE. (clj->js {...})] ;;-> This will not be dynamic, so consider moving to returned fn if necessary
(fn [text issue-hints]
(if hints
[:textarea (special-hint-handler hints)]
[:textarea]
))))
So based on the comments, this would give you a watcher on issue-hints, and you could respond accordingly. The subscription does not necessarily need to be used on the DOM.

Block until a DOM event occurs with core.async

I've got a Clojurescript project where i need to block the whole thread execution until an DOM event occurs.
In this case, the event is DOMContentLoaded, which fire when the initial HTML document has been completely loaded and parsed. But it could be extended to any DOM (or non-DOM) event.
As i'm new to Clojurescript and async i wasn't sure how to solve this problem. My first guess was to use the core.async library. After some doc scraping, i came with that function:
(defn wait-dom-loading
[]
(let [c (async/chan)]
{1} (.addEventListener js/document "DOMContentLoaded" (fn [] (async/go (async/>! c true))))
{2} (async/go (async/<! c))))
The way i understand it is that {2} takes from chan c and is parked until the listener in {1} evaluates the function and puts a value in chan c.
As i barely understand how to do unit tests on asynchronous code (beside puting it in an (async done) expression and calling done when done) i can't verify if what i did is correct. I tried this snippet:
(do
(wait-dom-loading)
(-> (dommy/sel1 :p)
(dommy/set-text! "Loaded !")))
With a p block inside an html page, and noticed that the console complains about the js code trying to manipulate a DOM object that don't yet exists. That confirms that what i did didn't work as planned.
What does seems wrong in this example ?
Is this overkill ? Could i solve that with a smaller solution or even gasp a built-in funtion ?
Is putting my script at the bottom of my html page a not so bad practice ?
As this was my first question on stack overflow, i hope it is well-written enough.
This is how i would do it:
(ns domevent.core
(:require [cljs.core.async :as async :refer [chan]])
(:require-macros [cljs.core.async.macros :refer [go]]))
(enable-console-print!)
(def ch (chan))
(go
(pr "i am waiting")
(pr (<! ch))
(dommy/set-text! (dommy/sel1 :p) "Loaded !"))
(.addEventListener js/document "DOMContentLoaded" (fn [] (go (>! ch "hello"))))
Or more simply:
(let [ch (chan)]
(go
(pr "i am waiting")
(<! ch)
(dommy/set-text! (dommy/sel1 :p) "Loaded !"))
(.addEventListener js/document "DOMContentLoaded" #(close! ch)))
)
The point here is the ch, which is shared by reader and writer. When (<! ch) happens, there is nothing in ch yet, so this thread is parked (i.e. stops and waits for anything to appear in ch). Meanwhile, the DOMContentLoaded occurs, and the handler writes to the channel. Then the former thread continues.

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: deref cursors in event handlers?

I'm trying to understand when we must deref a cursor to get its value.
In the om cursors wiki it states that
event handlers are considered not part of the render phase, and therefore cursors in handlers should be drefed.
Same is shown in the Basic-tutorial:
(defn contact-view [contact owner]
(reify
om/IRenderState
(render-state [this {:keys [delete]}]
(dom/li nil
(dom/span nil (display-name contact))
(dom/button #js {:onClick (fn [e] (put! delete #contact))} "Delete")))))
But, in the TodoMVC code, the handlers (onclick, onchange...) use the cursor without derefing it:
(dom/button
#js {:className "destroy"
:onClick (fn [_] (put! comm [:destroy todo]))}))
So, what is the correct way?
Thank you.
Notice that delete and comm are not cursors but core.async channels. The put! operation adds a message to the channel which is handled here.
Cursors are a way of wrapping state (called app-state in om). There are two things you might want to do with that state:
Change the state: when you want to change your app-state, you call om/transact! or om/update! on one the cursors to the app-state (deref never needed). Om schedules this transaction to be shown in the next render phase.
Read the state: when reading during the render phase (inside the render and render-state functions, the cursor works like its value, i.e., you don't need to deref it. At any other point in time, the cursor might be being transacted on or have a schedule transaction, so you want to deref it to get the current value and not some inconsistent state.