Search code examples
clojurescript

How do I extract the body from an HTTP request in Clojure?


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:

enter image description here

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: enter image description here

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?


Solution

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

    firefox console screenshot

    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.