EQL (EDN Query Language) is Fulcro's query and mutation language, a subset of Datomic's pull query syntax with extensions for unions and mutations. All data in Fulcro is queried and manipulated from the UI using EQL.
Queries can be either:
[:a :b]
The simplest EQL queries are for properties "right here" in the current graph node. Properties are queried using keywords.
[:a :b]
This queries for properties :a
and :b
at the current node in the graph traversal. Property values can be any scalar data serializable in EDN.
Joins represent traversal of an edge to another node in the graph. The notation is a map with a single key (the local property holding the "pointer") whose value is the query for the remainder of the graph walk.
[{:children (comp/get-query Child)}]
get-query
to properly annotate sub-queries for normalizationUnions represent a map of queries where only one applies at a given graph edge. This enables dynamic queries that adjust based on actual data linkage.
(defsc PersonPlaceOrThingUnion [this props]
; lambda form required for unions
{:query (fn [] {:person/id (comp/get-query Person)
:place/id (comp/get-query Place)
:thing/id (comp/get-query Thing)})}
...)
(defsc Parent [this props]
{:query [{:person-place-or-thing (comp/get-query PersonPlaceOrThingUnion)}]})
{ :person-place-or-thing [:place/id 3]
:place/id { 3 { :place/id 3 :location "New York" }}}
[:place/id 3]
):place
selects the appropriate query from the union{ :person-place-or-thing [[:person/id 1] [:place/id 3]]
:person/id { 1 { :person/id 1 :person/name "Julie" }}
:place/id { 3 { :place/id 3 :place/location "New York" }}}
(defsc PersonPlaceOrThingUnion [this props]
{:ident (fn []
(cond
(contains? props :person/id) [:person/id (:person/id props)]
(contains? props :place/id) [:place/id (:place/id props)]
:else [:thing/id (:thing/id props)]))}
...)
The union component must detect the data type and render the appropriate child:
(let [page (first (comp/get-ident this))]
(case page
:person/id ((comp/factory PersonDetail) (comp/props this))
:place/id ((comp/factory PlaceDetail) (comp/props this))
:thing/id ((comp/factory ThingDetail) (comp/props this))
(dom/div (str "Cannot route: Unknown Screen " page))))
Mutations are data representations of abstract actions on the data model. They look like single-argument function calls where the argument is a parameter map.
[(do-something)]
[(do-something {:param1 "value" :param2 42})]
(ns app.mutations)
(defmutation do-something [params]
(action [env] ...)
(remote [env] ...))
(ns app.ui
(:require [app.mutations :as am]))
...
(comp/transact! this [(am/do-something {})])
Query elements support parameter maps, primarily useful when sending queries to servers.
[(:prop {:x 1})]
[({:child (comp/get-query Child)} {:x 1})]
Due to Clojure's list evaluation, use syntax quoting in code:
:query (fn [this] `[({:child ~(comp/get-query Child)} {:x 1})])
Idents can be used in queries as plain properties or joins.
[ [:person/id 1] ]
This pulls a table entry without normalization or following subqueries:
(defsc X [this props]
{:query [ [:person/id 1] ] }
(let [person (get props [:person/id 1]) ; NOT get-in
...
; person contains {:id 1 :person/phone [:phone/id 4]}
(defsc X [this props]
{:query [{[:person/id 1] (comp/get-query Person)}]}
(let [person (get props [:person/id 1])
...
; person contains {:id 1 :person/phone {:phone/id 4 :phone/number "555-1212"}}
This re-roots the graph walk at the ident's table entry and continues the subtree traversal.
Link queries allow starting "back at the root" node, useful for singleton data like UI locale or current user.
[ [:ui/locale '_] ]
Results in :ui/locale
in props with a value from the root database node.
[ {[:current-user '_] (comp/get-query Person)} ]
Pulls :current-user
with continued graph traversal.
Components using only ident/link queries need database presence:
(defsc LocaleSwitcher [this {:keys [ui/locale]}]
{:query [[:ui/locale '_]]
:initial-state {}} ; Required: empty map for database presence
(dom/div ...))
(defsc Root [this {:keys [locale-switcher]}]
{:query [{:locale-switcher (comp/get-query LocaleSwitcher)}]
:initial-state (fn [params] {:locale-switcher (comp/get-initial-state LocaleSwitcher)})}
(ui-locale-switcher locale-switcher))
Alternative to ident/link queries for specific scenarios:
(app/fulcro-app
{:shared {:pi 3.14} ; never changes
:shared-fn #(select-keys % :current-user)}) ; updates on root render
(defsc C [this props]
(let [{:keys [pi current-user]} (comp/shared this)]
; current-user will be denormalized from root props
...))
comp/force-root-render!
)EQL supports recursive queries for self-referential data structures.
...
- Recurse until no more links (with circular detection)(defsc Person [this props]
{:query (fn [] [:person/id :person/name {:person/friends ...}])}
...)
For circular relationships, calculate depth to prevent rendering issues:
(defsc Person [this {:keys [person/name spouse] :as props}]
{:query (fn [] [:person/id :person/name {:spouse 1}])}
(let [depth (or (comp/get-computed this :depth) 0)]
(dom/div
(dom/p name)
(when (and spouse (< depth 1))
(ui-person (comp/computed spouse {:depth (inc depth)}))))))
EQL expressions can be converted to/from AST for complex query manipulation.
(eql/query->ast query)
(eql/ast->query ast)
env
on client and server(defmutation do-thing [params]
(action [env] ...)
(remote [{:keys [ast]}] ast)) ; same as `true`
(defmutation do-thing [params]
(action [env] ...)
(remote [{:keys [ast]}] (eql/query->ast `[(do-other-thing)]))) ; change mutation
(defmutation do-thing [params]
(action [env] ...)
(remote [{:keys [ast]}] (assoc ast :params {:y 3}))) ; change parameters
get-query
for joins to ensure proper normalization metadata:initial-state
for components using only ident/link queries(defsc Person [this props]
{:query [:person/id :person/name :person/email]
:ident [:person/id :person/id]}
...)
(defsc Person [this props]
{:query [:person/id :person/name
{:person/address (comp/get-query Address)}
{:person/friends (comp/get-query Person)}]
:ident [:person/id :person/id]}
...)
(defsc Root [this props]
{:query [{:current-user (comp/get-query Person)}
{:all-people (comp/get-query Person)}
[:ui/loading-state '_]]
:initial-state (fn [params]
{:current-user (comp/get-initial-state Person {})
:all-people []})}
...)
This comprehensive guide covers all the essential EQL concepts needed for effective Fulcro development, from basic property queries to advanced recursive patterns and AST manipulation.
Can 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 |