Fulcro's Core API centers around manipulating the graph database. Most operations are CLJC and work in headless environments, independent of rendering.
db->tree
FunctionPrimary algorithm in com.fulcrologic.fulcro.algorithms.denormalize
Purpose: Run EQL query against normalized Fulcro database
(def sample-db
{:people [[:person/id 1] [:person/id 2]]
:person/id {1 {:person/name "Bob"}
2 {:person/name "Judy"}}})
(fdn/db->tree [{:people [:person/name]}] sample-db sample-db)
=> {:people [#:person{:name "Bob"} #:person{:name "Judy"}]}
(get-in db ident)
Simple property access:
(fdn/db->tree [:people] sample-db sample-db)
=> {:people [[:person/id 1] [:person/id 2]]}
Table access:
(fdn/db->tree [:person/id] sample-db sample-db)
=> #:person{:id {1 #:person{:name "Bob"}, 2 #:person{:name "Judy"}}}
As advanced select-keys:
(let [entity {:person/name "Joe" :person/age 42}]
(fdn/db->tree [:person/name] entity {}))
=> #:person{:name "Joe"}
EQL allows idents as query elements to "jump" to specific entities:
(fdn/db->tree [[:person/id 1]] {} sample-db)
=> {[:person/id 1] #:person{:name "Bob"}}
;; With joins
(fdn/db->tree [{[:person/id 1] [:person/name]}] {} sample-db)
=> {[:person/id 1] #:person{:name "Bob"}}
;; How Fulcro refreshes specific components
(let [starting-entity (get-in sample-db [:person/id 1])]
(fdn/db->tree [:person/name] starting-entity sample-db))
=> #:person{:name "Bob"}
tree->db
FunctionLocated in com.fulcrologic.fulcro.algorithms.normalize
Purpose: Convert arbitrary tree of data into normalized form
Use the component's query to indicate intent:
Components provide ident functions that specify where data should be stored:
(defsc Person [this props]
{:ident :person/id ; or [:person/id :person/id] or (fn [] [...])
:query [:person/id :person/name]})
get-query
adds component metadata for normalization:
(meta (comp/get-query Person))
=> {:component Person-class-with-ident-function}
(fnorm/tree->db Root {:root/people {:person/id 1 :person/name "Bob"}} true)
=> {:root/people [:person/id 1], :person/id {1 #:person{:id 1, :name "Bob"}}}
Solves the "empty database" problem at application startup.
(defsc Person [this props]
{:query [:person/id :person/name]
:ident :person/id
:initial-state (fn [{:keys [id name]}] {:person/id id :person/name name})})
(comp/get-initial-state Person {:id 1 :name "Bob"})
=> #:person{:id 1, :name "Bob"}
(defsc Root [this props]
{:query [{:root/people (comp/get-query Person)}]
:initial-state (fn [_] {:root/people [(comp/get-initial-state Person {:id 1 :name "Bob"})]})})
(comp/get-initial-state Root)
=> #:root{:people [#:person{:id 1, :name "Bob"}]}
Template (concise):
{:initial-state {:person/id :param/id :person/name :param/name}}
Lambda (flexible):
{:initial-state (fn [{:keys [id name]}] {:person/id id :person/name name})}
Fulcro initialization is essentially:
(let [data-tree (comp/get-initial-state Root)
normalized-tree (fnorm/tree->db Root data-tree true)]
;; Reset app state to normalized-tree
)
Fulcro's rendering is surprisingly simple:
(let [current-state {...normalized-database...}
denormalized-tree (fdn/db->tree (comp/get-query Root) current-state current-state)
root-factory (comp/factory Root)]
(js/ReactDOM.render (root-factory denormalized-tree) dom-node))
Rendering is literal reification of normalized database as UI. Most complexity is in optimizations (targeted re-renders).
Update state database so next render frame shows desired UI.
assoc-in
, etc. within mutationsmerge-component!
or load!
;; Add data to normalized database
(merge/merge-component! app Person {:person/id 3 :person/name "Sally"})
;; With targeting
(merge/merge-component! app Person person-data
:append [:root/people])
:replace [:root/edge] ; Replace single reference
:append [:root/people] ; Add to end of vector
:prepend [:root/people] ; Add to beginning
(targeting/append-to [:person/id 1 :person/spouse]) ; Specific location
(defmutation save-person [params]
(remote [env]
(-> env
(m/returning Person) ; Merge result using Person component
(m/with-target [:current-user])))) ; Target to specific location
Remember: any node reachable in ≤3 levels:
[:table id field]
- Entity property[:table id]
- Entire entity[:root-prop]
- Root propertyCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
Ctrl+k | Jump to recent docs |
← | Move to previous article |
→ | Move to next article |
Ctrl+/ | Jump to the search field |