I'm trying out om.next, trying to extend the example given in Components, Identity & Normalization.
The code in the example linked above contains of two lists that are backed by data in two different paths in the state. The example shows how the normalization of the data makes it possible to easily update normalized entities occurring in different views. In the code below I have removed the second list in the example below, since the behaviour shows up anyway.
Working example
The example below works and shows:
List A
- John, points: 4 +
- Mary, points: 0 +
- Bob, points: 0 +
where the + is a button that increases the point for the person.
(ns ui.core
(:require [om.next :as om :refer-macros [defui]]
[om.dom :as dom]
[goog.dom :as gdom]))
(def init-data
{:list/one [{:name "John" :points 0}
{:name "Mary" :points 0}
{:name "Bob" :points 0}]})
;; -----------------------------------------------------------------------------
;; Parsing
(defmulti read om/dispatch)
(defn get-people [state key]
(let [st @state]
(into [] (map #(get-in st %)) (get st key))))
(defmethod read :list/one
[{: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))})
;; -----------------------------------------------------------------------------
;; 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]
(let [{:keys [points name] :as props} (om/props this)]
(dom/li nil
(dom/label nil (str name ", points: " points))
(dom/button
#js {:onClick
(fn [e]
(om/transact! this
`[(points/increment ~props)]))}
"+")))))
(def person (om/factory Person {:keyfn :name}))
(defui ListView
Object
(render [this]
(let [list (om/props this)]
(apply dom/ul nil
(map person list)))))
(def list-view (om/factory ListView {:key-fn ffirst}))
(defui RootView
static om/IQuery
(query [this]
(let [subquery (om/get-query Person)]
`[{:list/one ~subquery}]))
Object
(render [this]
(let [{:keys [list/one]} (om/props this)]
(apply dom/div nil
[
(dom/h2 nil "List A")
(list-view one)
]))))
(def rootview (om/factory RootView))
;; wrapping the Root in another root (this fails)
(defui AnotherRoot
static om/IQuery
(query [this] `[~@(om/get-query RootView)])
Object
(render
[this]
(dom/div nil
(rootview (om/props this)))))
(def reconciler
(om/reconciler
{:state init-data
:parser (om/parser {:read read :mutate mutate}) }))
(om/add-root! reconciler RootView (gdom/getElement "app"))
Problem: Using AnotherRoot as root component
However, when I change RootView in the last row to AnotherRoot, like:
(om/add-root! reconciler AnotherRoot (gdom/getElement "app"))
the ui still renders, but when pressing a button, the following error occurs:
Error: No queries exist for component path
(ui.core/AnotherRoot ui.core/RootView ui.core/Person)
The error comes from (next.cljc:1916)
I don't understand the error. The query from AnotherRoot
returns the same query as RootView
(however, you'll get a warning when returning the query straight away - which makes sense), but the reconciler seems to not be able to figure out how to re-render the component when the app-state changes.
The relevant dependencies should be:
[[org.clojure/clojure "1.9.0-alpha14"]
[org.clojure/clojurescript "1.9.473"]
[figwheel-sidecar "0.5.9"]
[org.omcljs/om "1.0.0-alpha47"]]
The question
What is the general way to create nested components in om.next?