Liking cljdoc? Tell your friends :D

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);
;; 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")))

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.

(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 Stuttaford
Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close