Components
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);
Inline components
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)))})
Component props
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
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})
children
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))
:ref attribute
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"))))
Class-based components
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.