Subscribe and add-watch in uberjar - clojurescript

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.

Related

Making an atom of a subscription requires derefing twice. Problem only occurs when using defonce

When I use def to save an atom, it works as expected. But when I use defonce, I have to deref twice: ##my-state. I want to use defonce because I want state preserved during reloads.
This works as expected
(def my-state (reagent/atom (re-frame/subscribe [::subs/photos])))
This requires two derefs to access the values
(defonce my-state (reagent/atom (re-frame/subscribe [::subs/photos])))
Subscription code
(re-frame/reg-sub
::photos
(fn [db [_]]
(:photos db)))
I want to use defonce because I want state preserved during reloads.
Then just don't use Reagent's atoms, use re-frame's subscriptions instead. They derive their values from re-frame's app-db which is itself defined with defonce.

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.

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.

Can name munging be avoided for an interop call in ClojureScript?

In advanced compilation
(js/console.log "HELLO"
js/window.navigator.msSaveBlob
(.. js/window -navigator -msSaveBlob)
(aget js/window "navigator" "msSaveBlob")
js/console.log)
=>
HELLO undefined undefined function function
I think this means that js/console has some provided externs, but navigator does not (or at least not the ms specific stuff).
AFAIK the only way to avoid this is to create some externs? But this seems unnecessarily obtuse; why would you ever want js/anything to be munged?? Wouldn't it make make more sense to never munge js/anything interop?
System functions are not munged; only your own functions are. You probably want (.log js/console ...) ?
For de-munging your own functions, place ^:export between the defn and the function name to export its name intact.
Here is more information.
All see the section called "munging" here.

is this swing tablemodel code badly designed?

Context: I have a clojure-based crossword app whose main ui is a JTabbedPane with two tabs, a grid and a clue table. The clue table is a view over a vector of clues, but the vector itself is not the authoritative store of the data, but dynamically generated from a couple of internal data structures via an (active-cluelist) function, triggered by the clue tab being selected.
So this is the implementation of the clue table:
(def cluelist [])
(def update-cluelist)
(def model)
(defn make []
(let [column-names ["Sq" "Word" "Clue"]
column-widths [48 200 600]
table-model (proxy [AbstractTableModel] []
(getColumnCount [] (count column-names))
(getRowCount [] (count cluelist))
(isCellEditable [row col] (= col 2))
(getColumnName [col] (nth column-names col))
(getValueAt [row col] (get-in cluelist [row col]))
(setValueAt [s row col]
(let [word (get-in cluelist [row 1])]
(add-clue word s) ; editing a cell updates the main clue data
(def cluelist (assoc-in cluelist [row 2] s))
(. this fireTableCellUpdated row col))))
table (JTable. table-model)
]
; some pure display stuff elided
(def model table-model)
)
(defn update-cluelist []
(def cluelist (active-cluelist))
(.fireTableDataChanged model))
Someone in another discussion noted that it is a major code smell for (update-cluelist) to be manually calling fireTableDataChanged, because nothing outside the TableModel class should ever be calling that method. However, I feel this is an unavoidable consequence of the table being dynamically generated from an external source. The docs aren't too helpful - they state that
Your custom class simply needs to invoke one the following
AbstractTableModel methods each time table data is changed by an
external source.
which implicitly assumes that the CustomTableModel class is the authoritative source of the data.
Also there is a bit of a clojure/java impedance mismatch here - in java I would have had cluelist and update-cluelist be a private member and method of my TableModel, whereas in clojure cluelist and the table model are dynamically scoped vars that update-cluelist has access to.
My main problem is that there is not a lot of clojure/swing code around that I can look to for best practices. Does anyone have any advice as to the best way to do this?
Suggestion: use an atom for cluelist. Constantly redefining cluelist is not the right way to represent mutable data. Honestly, I would expect it to throw an exception the second time you define cluelist.
If you use an atom for cluelist, you can call the fireTableDataChanged method from a watcher instead of calling it manually. This would mean that anytime (and anywhere) you change the atom, fireTableDataChanged will be called automatically, without an explicit call.
The issue with def is that calling def multiple times doesn't work well in a multi-threaded environment and Clojure tries to make everything default to fairly threadsafe. As I understand it, the "proper" way to use a var is to leave its root binding alone (ie, don't call def again) and use binding if you need to locally change it. def may work the way you are using it, but the language is set up to support atoms, refs, or agents in this sort of situation and these will probably work better most of the time (ie you get watchers). Also, you don't need to worry at all about threads if you add them later.