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

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.

Related

Clojure map returned by JDBC becomes null after inspection

I'm writing a simple URL shortener in Clojure, using Ring, Compojure, clojure.java.jdbc and MySQL.
I'm seeing a very strange issue where some inputs seem to randomly become null midway through a function, causing my checks to fail.
My code:
(defn redirect-handler [slug]
(if (not slug)
(response/bad-request "Must provide slug."))
(let [mapping (db/get-slug slug)]
;; this passes fine
(if mapping
(println (str mapping)))
;; this always calls the else case for some slugs, but not others
(if mapping
(response/redirect (:url mapping))
(do
(println "Not running. Mapping: " mapping)
(response/not-found (str "Slug not found: " slug))))))
For certain inputs, it always returns 404 with "Slug not found: ". Logs reveal very strange behaviour:
{:slug "eel", :url "eel.com"}
Not running. Mapping: nil
And the response is 404 with message Slug not found: eel.com - even stranger, since it seems to be returning the url instead of the slug in the response. It's almost as though the data is being modified midway through the function.
I have already confirmed the data in the database is correct.
My DB code:
(def mysql-db (edn/read-string (slurp "env.edn")))
(def query-slug-sql "SELECT * FROM urls WHERE slug = ?")
(defn get-slug [slug]
(first (j/query mysql-db [query-slug-sql slug])))
My HTTP routing code:
(defroutes app-routes
(GET "/:slug" [slug] (redirect-handler slug))
(GET "/" [] (response/not-found "Must provide slug."))
(POST "/create" [slug url] (create-handler slug url)))
(def app
(-> app-routes
(json/wrap-json-params)
(json/wrap-json-response)))
Any idea what is happening here?
I understand your confusion, because given the code you posted, a single call to redirect-handler can't possibly produce those log messages, no matter what value it receives as argument, and no matter what is returned by db/get-slug. Local variables just can't change value at all, and there's no single value in Clojure that goes from truthy to falsey, ever.
I can think of two explanations (maybe there are others):
The code you posted isn't the code that's running. Maybe you rewrote the function but didn't reload it, for example.
The log message isn't exclusively coming from redirect-handler. Maybe some other function prints the map on line one, and then the second line is the only thing printed by redirect-handler.

Why need to return a function in Reagent component?

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".

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.

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.