Having trouble correctly manipulating state in Om app - clojurescript

I'm having trouble manipulating state in an Om app. Specifically, I can't figure out how to remove items from a list.
Here's a demonstration of my folly. It's a simplified app that's broken. https://gist.github.com/rerb/29d10959e71ba1e31e8e
Two buttons are displayed. When pressed, they should remove themselves.
After deleting the first item, I get this error when trying to delete the second:
Uncaught Error: No protocol method IDeref.-deref defined for type cljs.core/PersistentArrayMap: {:id 2}
If I delete the second one first, I get this error when trying to delete the first:
Uncaught Error: Assert failed: Can't put nil in on a channel
What simple thing am I missing? I'm Einstellung.

The function remove returns a sequence, not a vector. Your state went from {:buttons [{:id 1} {:id 2}]} to {:buttons ({:id 1})}. By using into after remove you solve your problems:
(fn [buttons]
(into [] (remove #(= button-id (:id %)) buttons))))
Note: I tried this with Chestnut and it seems to work. Also, if you are beginning with Om avoid using core.async in the beginning. It is a great complement to Om but using both while learning was too much for me to handle.

Related

Why is db printing #object[Object [object Object]] in reframe?

In the following code I'm dispatching two on-click events:
;; event
(reg-event-db
:some
(fn [db [_ some-val]]
(prn "The db is" db)
(assoc db :some some-val)
))
;; another event
(reg-event-db
:another
(fn [db [_ another-val]]
(prn "The db is" db)
(assoc db :another another-val)
))
;; button
[:input {:type "button" :value "Button"
:on-click #(do
(dispatch [:some :some-val])
(dispatch [:another :another-val]))}]
But instead of printing the db map, it prints "The db is" #object[Object [object Object]], and then
Error: No protocol method IAssociative.-assoc defined for type object: [object Object]
What am I doing wrong? I also tried doing #(dispatch [:some :some-val :another another-val] but that gave the same error. In general, how to correctly dispatch two events?
This is correct:
(do
(dispatch [:first-event :some-value])
(dispatch [:second-event :other-value]))
This is not correct:
(dispatch [:some :some-val :another another-val])
In this example, you are dispatching one event with 3 arguments:
:some is the event name,
:some-val is the first argument,
:another is the second argument,
another-val is the 3rd argument.
The error you are encountering doesn't come from the way you dispatch events, but rather from your db state. Let's dissect it step by step:
If (prn db) outputs #object[Object [object Object]] it means db is a JavaScript object.
If (assoc db …) fails with No protocol method IAssociative.-assoc defined for type object: [object Object], it means the db value does not support the assoc function. The assoc function is defined by a protocol (think interface) and this protocol is implemented on maps. So here, db is not a map.
Why would db be a JavaScript object instead of a Clojure map?
When you use (reg-event-db :event-name handler-function), the value returned by handler-function will replace the db state. If by mistake you return a JS object, it will become the new db value, and this new incorrect db value will get passed to subsequent event handlers. So it's highly probable that one of your event handlers is returning a JavaScript object.
How I would solve this situation:
use (js/console.log db) instead of (prn db). You'll be able to see what's inside this js object. prn doesn't know how to print js objects properly.
make sure cljs-devtools is installed and running. It allows you to explore complex objects in the console. Next time this issue arises, you'll immediately see what's the problematic value instead of an opaque string representation.
use reg-event-fx instead of reg-event-db. While a little bit more verbose, it will make more explicit to the human eye what value you are putting in db.
if you want to go further and ensure this never happens again, take a look at Re-Frame Middlewares. In your case, middlewares would allow you to act on handlers returned values, and potentially reject it if it doesn't match your expectations.

ClojureScript - Failing to iterate over the ns-unmap function

I'm having trouble getting something to work in ClojureScript that works in Clojure no problem.
(defn unmap [nspace & vars]
(doseq [var vars] (ns-unmap nspace var)))
Usage as:
(unmap 'some.nspace 'vars 'to 'be 'removed)
You can also make unmap a macro so you don't have to quote everything. All it does is iterate over a list of symbols and pluck their bindings from a given namespace.
Neither the macro form, or the unmap function work in ClojureScript (they work fine in Clojure). I get the following error when trying to define the above function.
Unexpected error (AssertionError) macroexpanding cljs.core/ns-unmap.
Assert failed: Arguments to ns-unmap must be quoted symbols
Not sure if this is a bug. It seems like ClojureScript is looking for a type hint. If anyone knows how to get this to work it would be appreciated.
In ClojureScript, the ns-unmap macro is designed to be used at the REPL to manipulate both runtime and compiler state used in the implementation of namespaces at dev time.
The symbols passed to ns-unmap need to be known at compile time in order for it to properly manipulate compiler state at macroexpansion time.
This problem can be solved by defining a macro which expands to a do containing each of the desired ns-unmap invocations.
Here is an example:
(defmacro unmap [nspace & vars]
`(do
~#(map (fn [var]
`(ns-unmap ~nspace ~var))
vars)))

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?

How to use an atom which has the result of a http-request in reagent

This is what I'm using to make a remote call using the clj-http library.
(defn make-remote-call [endpoint]
(go (let [response (<! (http/get endpoint
{:with-credentials? false}))])))
(reset! app-state response)
;(js/console.log (print response)))))
The above print to console works fine
(defn call []
(let [x (r/atom (make-remote-call site))]
(js/console.log x)
this spits out #object[cljs.core.async.impl.channels.ManyToManyChannel] in the console.
What can I do to return the response in the make-remote-call function.
I used the response to set an atom's value. Trying to reference values inside the atom results in errors like "Uncaught Error: [object Object] is not ISeqable" and No protocol method IDeref.-deref defined for type null:
Any idea what I might be doing wrong?
Please let me know if I need to provide any additional info
make-remote-call is returning a channel. Try interrogating this channel to see what's inside it.
This question should help:
Why do core.async go blocks return a channel?
I think you know this already, but you need to de-reference an atom i.e. get what is inside an atom, using #. Infomally speaking, the value you want is wrapped in two containers, so you need to get what's inside the atom, then what's inside the channel.

Using atom and future is creating race condition in my Clojure program

I have a web service written in Clojure. It has a simple GET method implemented, which returns back a JSON object representing the router's current position and different time counters.
My code has a bunch of atoms to keep track of time. Each atom represents different activities a machine might be doing at a given time. For example: calibrating, idle, stuck, or working:
(def idle-time (atom 0))
(def working-time (atom 0))
(def stuck-time (atom 0))
(def calibration-time (atom 0))
Towards the end I have a loop that updates the position and time counters every 15 seconds:
(defn update-machine-info []
(let [machine-info (parse-data-files)]
(update-time-counters machine-info)
(reset! new-state (merge machine-info
{:idleCounter #idle-time
:workingCounter #working-time
:stuckCounter #stuck-time
:calibrationCounter #calibration-time}))))
(loop []
(future
(Thread/sleep 15000)
(update-machine-info)
(recur)))
Currently this code runs into race condition, meaning the position and time counters are not updating. However, the Web Service still responses back a proper JSON response, albeit with old values.
Web Service is using Cheshire to generate map into JSON, here my GET implementation:
(defroutes app-routes
(GET "/" [] (resource :available-media-types ["application/json"]
:handle-ok (generate-string (get-machine-information))))
(route/not-found "Not Found"))
Should I be using refs instead of atoms? Am I using future correctly? Is (Thread/sleep 15000) causing the issue, because atoms are async?
Please let me know if you see an obvious bug in my code.
I don't think you can reliably recur inside a future to a loop that's outside the future (not completely sure), but why not try something like this instead?
(future
(loop []
(Thread/sleep 15000)
(update-machine-info)
(recur)))
That way loop/recur stays within the same thread.
Other than that, it's possible that if update-machine-counters throws an exception the loop will stop, and you'll never see the exception because the future is never dereferenced. An agent ( http://clojure.org/agents ) might be better suited for this, since you can register an error handler.
I think what is happening is that the process where you call your futures is terminating before your futures actually execute. For what your doing, futures are probably the wrong type of construct. I also don't think your loop future recor sequence is doing what you think.
There is a lot of guesswork here as it isn't clear exactly where you are actually defining and calling your code. I think you probably want to use something like agents, which you need to setup in the root process and then send a message to them in your handler before you return your response.