Why is the counter in the child component updating fine when I comment
(om/update-state! owner :clicked not)
and not when I uncomment it in the parent component in the code below? The counter is updated by clicking the button.
What I'm trying to accomplish is a pub/sub mechanism so components can exchange messages in a decoupled fashion.
You can replicate it by making a new project with:
lein new mies-om om-channel-test
Then replace core.cljs with code below and run
lein cljsbuild auto
Visit the index.html page in a modern browser (for example the latest Chrome).
The code:
(ns om-channel-test.core
(:require-macros [cljs.core.async.macros :refer (go)])
(:require [om.core :as om :include-macros true]
[om.dom :as dom :include-macros true]
[cljs.core.async :refer [chan pub <! sub >! timeout put!]]))
(enable-console-print!)
(def app-state (atom {:text "Hello world!"}))
(def event-ch (chan))
(def event-pub
(pub event-ch #(:topic %)))
(defn child [cursor owner]
(reify
om/IInitState
(init-state [_]
{:counter 0})
om/IWillMount
(will-mount [_]
(go (loop [] (<! (om/get-state owner :subscriber))
(println "message received")
(om/update-state! owner :counter inc)
(recur))))
om/IRender
(render [_]
(println "rendering child")
(dom/p nil (om/get-state owner :counter)))
om/IWillUnmount
(will-unmount [_]
(println "unmount"))))
(defn parent [cursor owner]
(om/component
(println "rendering parent")
(dom/div nil
(dom/button #js {:onClick
#(do
#_(om/update-state! owner :clicked not)
(go (>! event-ch {:topic :wizard
:message "hello"})))}
"Click")
(om/build child
cursor
{:init-state
{:subscriber
((om/get-shared owner :create-subscriber) :wizard)}}))))
(om/root
parent
app-state
{:target (. js/document (getElementById "app"))
:shared {:create-subscriber (fn [topic]
(sub event-pub
topic (chan)))
:event-ch event-ch}})
Answered on https://groups.google.com/forum/#!topic/clojurescript/5rCTfnulNXI.
With line 41 uncommented the following seems to happen:
Parent component's state changed
om/react
"walks" the component tree in parent's render to see what should update
on line 45 with om/build
for the child component finds that the child component already exists, so there is no new component created nor mounted.
However, "running"/calling om/build
on line 45 created a new subscription to the event-pub
through :subscriber/:create-subscriber
in {:init-state ...}
There will not be a new component created that would create a go-loop to consume from this new subscriber channel (there's no call to om/will-mount
for a new component from line 22)
Now event-pub
has two subscribers but only one go-loop
that consumes from a channel. The pub on :event-ch
will block [1] [2]
Weirdness on the page
Seems you shouldn't have side-effects in the {:init-state ...}
passed to om/build
. Instead pass the event-pub
to the child component via :init-state
and create the sub chan together with the go-loop
to consume from it.
[1] http://clojure.github.io/core.async/#clojure.core.async/pub "Each item is distributed to all subs in parallel and synchronously, i.e. each sub must accept before the next item is distributed. Use buffering/windowing to prevent slow subs from holding up the pub."
[2] Play around with buffering in the chan on line 57 to see this behavior change for a couple of clicks