I’m a bit confused about updating app state from remote. I’ve made a simulated a remote which responds after some delay on a modified code from https://github.com/omcljs/om/wiki/Components,-Identity-&-Normalization. I simply put random values for age and points and called (cb (rewrite ...))
. But the view is not updated although the :person/by-name
referred by ident changed, the generated values are rendered only after the next transact. This is the changed code:
(ns om-learning.cards.tutorial
(:require [om.next :as om :refer-macros [defui]]
[om.dom :as dom])
(:require-macros [devcards.core :refer [defcard defcard-doc defcard-om-next]]))
(enable-console-print!)
(def init-data
{:list/one [{:name "John" :points 0}
{:name "Mary" :points 0}
{:name "Bob" :points 0}]
:list/two [{:name "Mary" :points 0 :age 27}
{:name "Gwen" :points 0}
{:name "Jeff" :points 0}]})
;; -----------------------------------------------------------------------------
;; Parsing
(defmulti read om/dispatch)
(defn get-people [state key]
(let [st @state
res (into [] (map #(get-in st %)) (get st key))]
(println "Key: " key)
(println "St: " st)
(println "Res: " res)
(println "(get st key): " (get st key))
res
))
(defmethod read :list/one
[{:keys [state] :as env} key params]
(do
(print "READ state: " @state)
(print "READ key: " key)
{:value (get-people state key)}))
(defmethod read :list/two
[{:keys [state] :as env} key params]
{:value (get-people state key)})
(defmulti mutate om/dispatch)
(defmethod mutate 'points/increment
[{:keys [state]} _ {:keys [name]}]
{:action
(fn []
(swap! state update-in
[:person/by-name name :points]
inc))
:remote true})
(defmethod mutate 'points/decrement
[{:keys [state]} _ {:keys [name]}]
{:action
(fn []
(swap! state update-in
[:person/by-name name :points]
#(let [n (dec %)] (if (neg? n) 0 n))))})
;; -----------------------------------------------------------------------------
;; Components
(defui Person
static om/Ident
(ident [this {:keys [name]}]
[:person/by-name name])
static om/IQuery
(query [this]
[:name :points :age])
Object
(render [this]
(println "Render Person" (-> this om/props :name))
(let [{:keys [points name age] :as props} (om/props this)]
(dom/li nil
(dom/label nil (str name ", age: " (or age "?") ", points: " points))
(dom/button
#js {:onClick
(fn [e]
(om/transact! this
`[(points/increment ~props)]))}
"+")
(dom/button
#js {:onClick
(fn [e]
(om/transact! this
`[(points/decrement ~props)]))}
"-")))))
(def person (om/factory Person {:keyfn :name}))
(defui ListView
Object
(render [this]
(println "Render ListView" (-> this om/path first))
(let [list (om/props this)]
(apply dom/ul nil
(map person list)))))
(def list-view (om/factory ListView))
(defui RootView
static om/IQuery
(query [this]
(let [subquery (om/get-query Person)]
`[{:list/one ~subquery} {:list/two ~subquery}]))
Object
(render [this]
(println "Render RootView")
(let [{:keys [list/one list/two]} (om/props this)]
(apply dom/div nil
[(dom/h2 nil "List A")
(list-view one)
(dom/h2 nil "List B")
(list-view two)]))))
(defn send [{:keys [remote]} cb]
(let [{:keys [rewrite]} (om/process-roots remote)]
(js/setTimeout
(fn []
(let [next-age (rand-int 100) next-points (rand-int 10)]
(println "REWRITING age:" next-age "points:" next-points)
(cb (rewrite
{:person/by-name
(into {}
(map (fn [x] [x {:name x :points next-points :age next-age}])
["John" "Mary" "Bob" "Gwen" "Jeff"]))})))
) 1500)))
(def reconciler
(om/reconciler
{:state init-data
:parser (om/parser {:read read :mutate mutate})
:send send}))
(defcard (-> Person om.next/get-query meta))
(defcard init-data-card init-data)
(defcard
(om/tree->db RootView init-data true))
(defcard-om-next
root-view
RootView reconciler)
In that code the send
function and the points/increment
mutator is changed to the tutorial.
I certainly have incorrect expectations about app state updates. I’ve seen watches on atoms in the om-next code and thought that changes on keys referred by indent will queue components to re-render. It’s obviously not so. How can I reach that updates coming from a remote will be rendered?