All HTML5 elements have factory functions in com.fulcrologic.fulcro.dom
:
(ns app.ui
(:require [com.fulcrologic.fulcro.dom :as dom]))
(dom/div :.some-class
(dom/ul {:style {:color "red"}}
(dom/li "Item")))
For files that run on both client and server:
(ns app.ui
(:require #?(:clj [com.fulcrologic.fulcro.dom-server :as dom]
:cljs [com.fulcrologic.fulcro.dom :as dom])))
Basic forms:
(dom/div "Hello") ; no props
(dom/div nil "Hello") ; explicit nil props (slight performance benefit)
(dom/div #js {:data-x 1} ...) ; JavaScript objects allowed
CSS shorthand:
(dom/div :.cls.cls2#id ...) ; classes and ID
(dom/div :.cls {:data-x 2} "Ho") ; shorthand + props
Dynamic classes:
(dom/div :.a {:className "other"
:classes [(when hidden "hidden")
(if tall :.tall :.short)]} ...)
Macro vs Function:
(mapv dom/div ["a" "b"]) ; function - no source annotation
(dom/div nil "a") ; macro - has source annotation
(dom/div {} "a") ; macro - has source annotation
Optimal performance:
(dom/div :.a {} (f)) ; no ambiguity - fastest compile-time conversion
(dom/div :.a (f)) ; ambiguous - runtime checks required
defsc
MacroMain macro for creating stateful components with sanity checking.
(defsc ComponentName [this props computed extra]
{:query [...] ; optional
:ident [...] ; optional
:initial-state {...}} ; optional
(dom/div (:some-prop props)))
this
: Component instance (required)props
: Component data (required)computed
: Parent-computed data like callbacks (optional)extra
: Extension point for libraries (optional)(defsc Person [this
{:keys [db/id person/name] :as props}
{:keys [onClick] :or {onClick identity}}]
{:query [:db/id :person/name]}
(dom/div {:onClick onClick} name))
Keyword form (recommended):
{:ident :person/id} ; table name = ID key name
Template form:
{:ident [:person/id :person/id]} ; [table-name id-key]
Lambda form (flexible):
{:ident (fn [] [:person/id (:person/id props)])}
Template form (recommended):
{:query [:person/id :person/name {:person/address (comp/get-query Address)}]}
Lambda form (for complex queries):
{:query (fn [] [:person/id :person/name])} ; Required for unions, wildcards
Template form:
{:initial-state {:person/id :param/id
:person/name :param/name
:person/address {:address/street "123 Main St"}}}
Lambda form:
{:initial-state (fn [{:keys [id name]}]
{:person/id id :person/name name})}
Auto-composition in template:
;; To-one relation
{:query [{:person/address (comp/get-query Address)}]
:initial-state {:person/address {:address/street "123 Main St"}}}
;; To-many relation
{:query [{:person/phones (comp/get-query Phone)}]
:initial-state {:person/phones [{:phone/number "555-1234"}
{:phone/number "555-5678"}]}}
Manipulate data entering the app at component level.
(defsc Person [this props]
{:pre-merge (fn [{:keys [data-tree current-normalized state-map query]}]
;; Return modified data
(assoc data-tree :ui/expanded false))}
...)
Always provide :key
for collections:
(map (fn [person]
(ui-person (assoc person :react-key (:person/id person))))
people)
;; Or use factory keyfn
(def ui-person (comp/factory Person {:keyfn :person/id}))
(map ui-person people)
Follow React conventions:
:className
instead of :class
:onClick
instead of :onclick
:htmlFor
instead of :for
(def ui-person (comp/factory Person))
;; With automatic keys
(def ui-person (comp/factory Person {:keyfn :person/id}))
;; For computed props
(def ui-person (comp/computed-factory Person))
;; Basic usage
(ui-person {:person/name "Joe" :person/age 30})
;; With computed props
(ui-person (comp/computed person-data {:onClick delete-fn}))
;; Computed factory (two arguments)
(ui-person person-data {:onClick delete-fn})
Common patterns for translating HTML:
HTML:
<div class="container" id="main">
<p>Hello <strong>World</strong></p>
</div>
Fulcro:
(dom/div :.container#main
(dom/p "Hello " (dom/strong "World")))
class="x y"
→ :className "x y"
or :.x.y
<input type="text">
→ (dom/input {:type "text"})
onClick
, onChange
, etc.All React lifecycle methods available as component options:
(defsc MyComponent [this props]
{:componentDidMount (fn [this] ...)
:componentWillUnmount (fn [this] ...)
:componentDidUpdate (fn [this prev-props prev-state] ...)}
...)
(defsc PersonList [this props]
{:componentDidMount
(fn [this]
(df/load! this :people Person))} ; Load data on mount
...)
For presentation-only components:
(defn my-header [title]
(dom/div :.header
(dom/h1 title)))
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 |