Search code examples
reactjsclojurescriptcore.asyncom

using core.async / ajax data in om component


I am currently experimenting with om and try to load external data for displaying it in a component.

My detail component:

(defn detail-component [app owner opts]
  (reify
    om/IInitState
    (init-state [_]
                (om/transact! app [:data] (fn [] "Test")))
    om/IWillMount
    (will-mount [_]
      (go (let [foo (<! (fetch-something 1))]
           (om/update! app #(assoc % :data foo))
           )))
    om/IRender
    (render [_]
      (dom/div nil
               (dom/h1 nil "Foo")
               (om/build another-component app)
               )
      ))
  )

(fetch-something is retrieving data from an API).

(defn another-component [{:keys [data]}]
  (om/component
    (.log js/console data)
    (dom/h2 nil "Another component goes here")
    (dom/h2 nil (data :description))
    )
  )

So to summarize, detail-component fetches data before mounting, attaches it to app and builds another-component. another-component then takes the description out of that data and displays it.

However when executing, I am getting Uncaught TypeError: Cannot read property 'call' of null at the point where I am trying to access the description. This indicates to me that at the point when another-component is getting built, the data is not there yet and it fails.

How can I tell om to build the app when data is available? Or do I have to build in some nil? checks?


Solution

  • Working example using state:

    project.clj

    (defproject asajax "0.0.1-SNAPSHOT"
      :description "FIXME: write description"
      :url "http://example.com/FIXME"
      :license {:name "Eclipse Public License - v 1.0"
                :url "http://www.eclipse.org/legal/epl-v10.html"
                :distribution :repo}
      :min-lein-version "2.3.4"
      :source-paths ["src/clj" "src/cljs"]
      :dependencies [[org.clojure/clojure "1.6.0"]
                     [org.clojure/clojurescript "0.0-2371"]
                     [org.clojure/core.async "0.1.267.0-0d7780-alpha"]
                     [om "0.7.3"]
                     [com.facebook/react "0.11.2"]]
      :plugins [[lein-cljsbuild "1.0.4-SNAPSHOT"]]
      :hooks [leiningen.cljsbuild]
      :cljsbuild
      {:builds {:asajax
                {:source-paths ["src/cljs"]
                 :compiler
                 {:output-to "dev-resources/public/js/asajax.js"
                  :optimizations :whitespace
                  :pretty-print true}}}})
    

    core.cljs

    (ns asajax.core
      (:require [om.core :as om :include-macros true]
                [om.dom :as dom :include-macros true]
                [cljs.core.async :as async])
      (:require-macros [cljs.core.async.macros :refer (go)]))
    
    (enable-console-print!)
    
    (def app-state (atom {}))
    
    (defn another-component [{:keys [data]}]
      (reify
        om/IRenderState
        (render-state [_ state]
          (dom/div nil
                   ;; (.log js/console data)
                   (dom/h2 nil "Another component goes here")
                   (dom/h2 nil (:description state))))))
    
    (defn fetch-something [x]
      (let [c (async/chan)]
        (go
          ;; pretend a blocking call
          ;; wait for 2 sec
          (<! (async/timeout 2000))
          (>! c {:data "Roast peach & Parma ham salad"
                 :description "This is a lovely light starter with fantastic sweet, salty and creamy flavours"}))
        c))
    
    (defn detail-component [app owner opts]
      (reify
        om/IInitState
        (init-state [_]
          {:data "Test"})
        om/IWillMount
        (will-mount [_]
          (go (let [foo (<! (fetch-something 1))]
                ;; (prn "GOT" foo)
                (om/set-state! owner foo))))
        om/IRenderState
        (render-state [_ state]
          (dom/div nil
                   (dom/h1 nil (:data state))
                   (om/build another-component app
                             {:state (om/get-state owner)})))))
    
    (om/root
     detail-component
     app-state
     {:target (. js/document (getElementById "app"))})
    

    UPDATE2

    Here is one using sablono and not using set-state!

    Add to project.clj

             [sablono "0.2.22"]
    

    Full core.cljs

    (ns asajax.core
      (:require [om.core :as om :include-macros true]
                [om.dom :as dom :include-macros true]
                [cljs.core.async :as async]
                [sablono.core :as html :refer-macros [html]])
      (:require-macros [cljs.core.async.macros :refer (go)]))
    
    (enable-console-print!)
    
    (def app-state (atom {:data "Initial"
                          :description "Loading..."}))
    
    (defn another-component [{:keys [description]}]
      (om/component
       (html
        [:.description
         [:h2 "Another component"]
         [:h2 description]])))
    
    (defn fetch-something [x]
      (let [c (async/chan)]
        (go
          ;; pretend a blocking call
          ;; wait for 2 sec
          (<! (async/timeout 2000))
          (>! c {:data "Roast peach & Parma ham salad"
                 :description "This is a lovely light starter with fantastic sweet, salty and creamy flavours"}))
        c))
    
    (defn detail-component [app owner opts]
      (reify
        om/IWillMount
        (will-mount [_]
          (go (let [foo (<! (fetch-something 1))]
                ;; (prn "GOT" foo)
                (om/update! app foo))))
        om/IRender
        (render [_]
          (html [:div
                 [:h1 (:data app)]
                 (om/build another-component app)
                 ]))))
    
    (om/root
     detail-component
     app-state
     {:target (. js/document (getElementById "app"))})