UIx components are defined using the defui
macro, which returns React elements created using the $
macro. The signature of $
macro is similar to React.createElement
, with an additional shorthand syntax in the tag name to declare CSS id and class names (similar to Hiccup):
// React without JSX
React.createElement("div", { onClick: f }, child1, child2);
;; UIx
($ :div#id.class {:on-click f} child1 child2)
(ns my.app
(:require [uix.core :refer [defui $]]))
(defui button [{:keys [on-click children]}]
($ :button {:on-click on-click}
children))
(defui text-input [{:keys [value type on-change]}]
($ :input {:value value
:type type
:on-change #(on-change (.. % -target -value))}))
(defui sign-in-form [{:keys [email password]}]
($ :form
($ text-input {:value email :type :email})
($ text-input {:value password :type password})
($ button {} "Sign in")))
Sometimes you might want to create an inline component using anonymous function. Let's take a look at the following example:
(defui ui-list [{{:keys [key-fn data item]}}]
($ :div
(for [x data]
($ item {:data x :key (key-fn x)}))))
(defui list-item [{:keys [data]}]
($ :div (:id data)))
($ ul-list
{:key-fn :id
:data [{:id 1} {:id 2} {:id 3}]
:item list-item})
In the example above ul-list
takes item
props which has to be a defui
component, which means you have to declare list-item
elsewhere.
With uix.core/fn
it becomes less annoying:
(defui ui-list [{{:keys [key-fn data item]}}]
($ :div
(for [x data]
($ item {:data x :key (key-fn x)}))))
($ ul-list
{:key-fn :id
:data [{:id 1} {:id 2} {:id 3}]
:item (uix/fn [{:keys [data]}]
($ :div (:id data)))})
defui
components are similar to React’s JSX components. They take props and children and provide them within a component as a single map of props.
Let's take a look at the following example:
function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
}
<Button onClick={console.log}>Press me</Button>;
The Button
component takes JSX attributes and the "Press me"
string as a child element. The signature of the component declares a single parameter which is assigned to an object of passed in attributes + child elements stored under the children
key.
Similarly in UIx, components take a map of props and an arbitrary number of child element. The signature of defui
declares a single parameter which is assigned a hash map of passed in properties + child elements stored under the :children
key.
(defui button [{:keys [on-click children]}]
($ :button {:on-click on-click}
children))
($ button {:on-click js/console.log} "Press me")
DOM attributes are written as keywords in kebab-case. Values that are normally strings without whitespace can be written as keywords as well, which may improve autocompletion in your IDE.
($ :button {:title "play button"
:data-test-id :play-button})
Similar to React, child components are passed as children
in the props map. children
is a JS Array of React elements.
(defui popover [{:keys [children]}]
($ :div.popover children))
Refs provide a way to refer to DOM nodes. In UIx ref
is passed as a normal attribute onto DOM elements, similar to React. use-ref
returns a ref with an Atom-like API: the ref can be dereferenced using @
and updated with either clojure.core/reset!
or clojure.core/swap!
.
(defui form []
(let [ref (uix.core/use-ref)]
($ :form
($ :input {:ref ref})
($ :button {:on-click #(.focus @ref)}
"press to focus on input"))))
UIx components don't take refs because they are built on top of React's function-based components which don't have instances.
When you need to pass a ref into child component, pass it as a normal prop.
(defui text-input [{:keys [ref]}]
($ :input {:ref ref}))
(defui form []
(let [ref (uix.core/use-ref)]
($ :form
($ text-input {:ref ref})
($ :button {:on-click #(.focus @ref)}
"press to focus on input"))))
Sometimes you want to create a class-based React component, for example an error boundary. For that there's the uix.core/create-class
function.
(def error-boundary
(uix.core/create-class
{:displayName "error-boundary"
:getInitialState (fn [] #js {:error nil})
:getDerivedStateFromError (fn [error] #js {:error error})
:componentDidCatch (fn [error error-info]
(this-as this
(let [props (.. this -props -argv)]
(when-let [on-error (:on-error props)]
(on-error error)))))
:render (fn []
(this-as this
(if (.. this -state -error)
($ :div "error")
(.. this -props -children))))}))
($ error-boundary {:on-error js/console.error}
($ some-ui-that-can-error))
Can you improve this documentation? These fine people already did:
roman01la, Shaun Mahood & Robert StuttafordEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close