My re-frame views.cljs has:
(re-frame/dispatch [::re-graph/init
{:http-url "https://api.spacex.land/graphql"
:ws-url nil
:http-parameters {:with-credentials? false}}])
(re-frame/dispatch [::re-graph/query
"{ launches { id, mission_name } }" ;; your graphql query
[::update-data]])
My events.cljs has:
(re-frame/reg-event-db
::update-data
(fn [db [_ {:keys [data errors] :as payload}]]
(-> db
(assoc :errors errors)
(assoc :data data))))
But I keep getting this error:
core.cljs:3919 re-frame: no :event handler registered for: undefined
The solution is to include the nil for the query variables
(re-frame/dispatch
[::re-graph/query
"{launches {id, mission_name}}"
nil
[:add-launches]])
You should use :events/update-data in views.cljs. The :: refers to the current namespace (:views/update-data), and that event handler is not defined there, but in the events namespace.
Also note that you can use:
(-> db
(assoc :errors errors
:data data)))
saves you one assoc.
Related
The question is how to send to a nodejs app the result of a go block
i found a solution with callback
but i need a solution with promise
Promise solution?
Clojurescript app
(defn foo []
(go 1))
;;how to change foo,wrap to promise?, so node app can await to get the 1
;;i used 1 for simplicity in my code i have something like
;;(go (let [x (<! ...)] x))
Node app
async function nodefoo() {
var x = await foo();
console.log(x); // i want to see 1
}
Callback solution (the one that works now)
So far i only found a way to pass a cb function, so this 1 goes back to node.js app
Clojurescript app
(defn foo1 [cb]
(take! (go 1)
(fn [r] (cb r))))
Node app
var cb=function () {....};
foo1(cb); //this way node defined cb function will be called with argument 1
But i dont want to pass a callback function, i want node.js to wait and get the value.
I want to return a promise.
This function takes a channel and returns a Javascript Promise that resolves with the first value the channel emits:
(defn wrap-as-promise
[chanl]
(new js/Promise (fn [resolve _]
(go (resolve (<! chanl))))))
Then to show usage:
(def chanl (chan 1))
(def p (wrap-as-promise chanl))
(go
(>! chanl "hello")
(.then p (fn [val] (println val))))
If you compile that and run it in your browser (assuming you called enable-console-print!) you'll see "hello".
It is also possible to extend the ManyToManyChannel type with extend-type.
Here's a naif implementation using a similar wrap-as-promise function
(require '[clojure.core.async.impl.channels :refer [ManyToManyChannel]])
(defn is-error? [val] (instance? js/Error val))
(defn wrap-as-promise
[channel callback]
(new js/Promise
(fn [resolve reject]
(go
(let [v (<! channel)]
(if (is-error? v)
(reject v)
(resolve (callback v))))))))
(extend-type ManyToManyChannel
Object
(then
[this f]
(wrap-as-promise this f)))
(def test-chan (chan 1))
(put! test-chan (new js/Error "ihihi"))
(put! test-chan :A)
(defn put-and-close! [port val]
(put! port val)
(async/close! port))
(-> test-chan
(.then (fn [value] (js/console.log "value:" value)))
(.catch (fn [e] (js/console.log "error" e)))
(.finally #(js/console.log "finally clause.")))
I am making an HTTP request:
(defn main-panel []
(def API-URL "https://api.chucknorris.io/jokes/random")
(defn getFileTree []
(go (let [response (<! (http/get API-URL
{:with-credentials? false
:headers {"Content-Type" "application/json"}}))]
(:status response)
(js/console.log (:body response))))) ; prints a very complex data structure
(go
(let [result (<! (getFileTree))]
(.log js/console (:body result)))) ; prints null
:reagent-render
(fn []
[:h1 "kjdfkjndfkjn"]))
But I can't get to the "joke" in the returned object, array item 13:
How do I assign this value to a let or def?
Also, why does the second console.log print null?
Update
I am now moving on from using reagent atoms to reframe.
This is my component that successfully GETs data, updates the re-frame 'database':
(defn main-panel []
(def API-URL "https://api.chucknorris.io/jokes/random")
(def request-opts {:with-credentials? false})
(defn getFileTree []
(go (let [response (<! (http/get API-URL request-opts))]
(re-frame/dispatch [:update-quote response]))))
(defn render-quote []
(println (re-frame/subscribe [::subs/quote])) ;successfully prints API data as in screenshot below
(fn []
(let [quote-data (re-frame/subscribe [::subs/quote])
quote-text (if quote-data (:value quote-data) "...loading...")]
[:div
[:h3 "Chuck quote of the day"]
[:em quote-text]])))
(fn []
(getFileTree)
[render-quote]))
But this is the object I get back from the re-frame database:
As you can see it comes wrapped in the Reaction tags and I can't access the body or value any more. How do I access those?
I have a small working version using the reagent template. Create a new project (assuming you have Leiningen installed) with: lein new reagent chuck. This will create a project with many dependencies, but it works out of the box.
Next, edit the file at src/cljs/chuck/core.cljs and edit it so it looks like the following:
(ns chuck.core
(:require-macros [cljs.core.async.macros :refer [go]])
(:require [reagent.core :as reagent :refer [atom]]
[cljs-http.client :as http]
[cljs.core.async :refer [<!]]))
(def api-url "https://api.chucknorris.io/jokes/random")
(def request-opts {:with-credentials? false
:headers {"Content-Type" "application/json"}})
(def api-response (atom nil))
(defn get-quote []
(go
(let [response (<! (http/get api-url request-opts))]
(println response)
(reset! api-response response))))
(defn render-quote []
(fn []
(let [quote-data (:body #api-response)
quote-text (if quote-data (:value quote-data) "...loading...")]
[:div
[:h3 "Chuck quote of the day"]
[:em quote-text]])))
(defn quote-page []
(fn []
(do
(get-quote)
[:div
[:header
[render-quote]]
[:footer
[:p "footer here"]]])))
;; -------------------------
;; Initialize app
(defn mount-root []
(reagent/render [quote-page] (.getElementById js/document "app")))
(defn init! []
(mount-root))
I'll explain the relevant bits:
init will bootstrap the basics of the front-end, but in our case it's just calls mount-root which starts reagent telling it to call quote-page and placing the results in the DOM replacing the element with the ID of app.
quote-page calls get-quote which will call the API using the cljs-http library. I'm not checking for errors here, but basically when the request completes (either success or error) it will read the results from the channel (using <!) and place the response in response. The key is that response is a nested ClojureScript map that you can inspect to check if the result was successful or not. Note that I'm also printing the results with println instead of JS interop (.log js/console xxx) because console.log will show the inner details of how the nested map is implemented, which is not relevant for this case.
One the response is available, I store the results of the response in an atom called api-response. The key here is that the atom will contain nothing for a bit (while the request completes) and then the response will be inside it and reagent will take care of detecting the change and re-rendering.
Finally, quote-page calls render-quote which generates the markup for rendering the quote or a placeholder while it loads.
To run the whole thing, open a terminal and run lein run which will start a web server listening on port 3000 by default. In another terminal, run lein figwheel which will compile the ClojureScript code for you. One figwheel is ready it will start a REPL, and you can open the address http://0.0.0.0:3000/ in your computer to view the page.
Questions
My webpage only has the output: {:user {}} with the following code.
(ns omn1.core
(:require
[om.next :as om :refer-macros [defui]]
[om.dom :as dom :refer [div]]
[goog.dom :as gdom]))
(defui MyComponent
static om/IQuery
(query [this] [:user])
Object
(render
[this]
(let [data (om/props this)]
(div nil (str data)))))
(def app-state (atom {:user {:name "Fenton"}}))
(defn reader [{q :query st :state} _ _]
(.log js/console (str "q: " q))
{:value (om/db->tree q #app-state #app-state)})
(def parser (om/parser {:read reader}))
(def reconciler
(om/reconciler
{:state app-state
:parser parser}))
(om/add-root! reconciler MyComponent (gdom/getElement "app"))
When I check the browser console, I notice that my query is nil. Why
doesn't it get passed into my reader function?
This comes from a motivation to keep my code to a minimal # of LOC as possible, and also DRY. So I'd like to have one read function that will work with a properly set up database, and normal nominal queries. If you pass regular queries to om/db->tree indeed db->tree does this. db->tree will take any proper query and return you a filled out tree of data. Maybe another way to phrase the question is can someone demonstrate a reader function that does this? I.e. leveraging db->tree to resolve the value of a query. I don't want to write a custom reader for each query I have. If all my queries obey the regular query syntax AND my DB is properly formatted, I should be able to use one reader function, no?
The example provided in the om.next quick start - thinking with links doesn't work:
(defmethod read :items
[{:keys [query state]} k _]
(let [st #state]
{:value (om/db->tree query (get st k) st)}))
as stated before query is nil sometimes, and the 2nd and 3rd arguments are different from what is proposed as how to use this function from the tests which all use: st for both 2nd and 3rd arguments. Confused.
From the Om.Next Quick Start tutorial (https://github.com/omcljs/om/wiki/Quick-Start-(om.next)), read has this signature:
[{:keys [state] :as env} key params]
So there is no access to a query data structure.
Usually the setup is to have a multimethod for each query, and use the query's params to return some part of the state:
(defmulti read (fn [env key params] key))
(defmethod read :animals/list
[{:keys [state] :as env} key {:keys [start end]}]
{:value (subvec (:animals/list #state) start end)})
Here :animals/list is the key of the query. So this is how you can access the key and params of the query.
I would like to display the html output of the following object:
(defn search-input [_ owner]
(reify
om/IInitState
(init-state [_]
{:text nil})
om/IRenderState
(render-state [this state]
(dom/input
#js {:type "text"
:value (:text state)
:className "form-control"
:onChange (fn [event] (handle-change event owner state))}))))
There is a render-to-str method in om.dom. But if I type
om.dom/render-to-str
in the ClojureScript repl all I get is nil. And calling om.dom/render-to-str gives the correspondign error message.
TypeError: 'undefined' is not an object (evaluating 'om.dom.render_to_str.call')
The strange thing: Code completion in the repl gives me the render-to-str call.
Ok the problem with om.dom/render-to-str returning nil is solved. The problem was that I didn't connect to a real browser repl but a headless repl. Therefore no index.html was loaded and therefore not react.js was loaded.
But now calling
(dom/render-to-str (search-input nil {}))
returns
"Error evaluating:" (dom/render-to-str (search-input nil {})) :as "om.dom.render_to_str.call(null,om_oanda.core.search_input.call(null,null,cljs.core.PersistentArrayMap.EMPTY));\n"
#<Error: Invariant Violation: renderComponentToString(): You must pass a valid ReactComponent.>
Error: Invariant Violation: renderComponentToString(): You must pass a valid ReactComponent.
After some more tests I think I have to change the call like this:
(dom/render-to-str (om.core/build search-input a-cursor {}))
So the last question is: How do I create a cursor.
(defn render-to-str
"Equivalent to React.renderComponentToString"
[c]
(js/React.renderComponentToString c))
Try calling the function with the component as an argument.
I wish to throw an exception and have the following:
(throw "Some text")
However it seems to be ignored.
You need to wrap your string in a Throwable:
(throw (Throwable. "Some text"))
or
(throw (Exception. "Some text"))
You can set up a try/catch/finally block as well:
(defn myDivision [x y]
(try
(/ x y)
(catch ArithmeticException e
(println "Exception message: " (.getMessage e)))
(finally
(println "Done."))))
REPL session:
user=> (myDivision 4 2)
Done.
2
user=> (myDivision 4 0)
Exception message: Divide by zero
Done.
nil
clojure.contrib.condition provides a Clojure-friendly means of handling exceptions. You can raise conditons with causes. Each condition can have its own handler.
There are a number of examples in the source on github.
It's quite flexible, in that you can provide your own key, value pairs when raising and then decide what to do in your handler based on the keys/values.
E.g. (mangling the example code):
(if (something-wrong x)
(raise :type :something-is-wrong :arg 'x :value x))
You can then have a handler for :something-is-wrong:
(handler-case :type
(do-non-error-condition-stuff)
(handle :something-is-wrong
(print-stack-trace *condition*)))
If you want to throw an exception and include some debugging info in it (in addition to a message string), you can use the built-in ex-info function.
To extract the data from the previously-constructed ex-info object, use ex-data.
Example from clojuredocs:
(try
(throw
(ex-info "The ice cream has melted!"
{:causes #{:fridge-door-open :dangerously-high-temperature}
:current-temperature {:value 25 :unit :celcius}}))
(catch Exception e (ex-data e))
In a comment kolen mentioned slingshot, which provides advanced functionality that allows you not only to throw objects of arbitrary type (with throw+), but also use a more flexible catch syntax to inspect data inside thrown objects (with try+). Examples from the project repo:
tensor/parse.clj
(ns tensor.parse
(:use [slingshot.slingshot :only [throw+]]))
(defn parse-tree [tree hint]
(if (bad-tree? tree)
(throw+ {:type ::bad-tree :tree tree :hint hint})
(parse-good-tree tree hint)))
math/expression.clj
(ns math.expression
(:require [tensor.parse]
[clojure.tools.logging :as log])
(:use [slingshot.slingshot :only [throw+ try+]]))
(defn read-file [file]
(try+
[...]
(tensor.parse/parse-tree tree)
[...]
(catch [:type :tensor.parse/bad-tree] {:keys [tree hint]}
(log/error "failed to parse tensor" tree "with hint" hint)
(throw+))
(catch Object _
(log/error (:throwable &throw-context) "unexpected error")
(throw+))))