Simple and Decomplected UI library based on React (originated as fork from rum).
Add to deps.edn:
funcool/rumext {:mvn/version "2020.03.23-1"}
This is the list of the main differences that rumext introduces vs rum:
WARNING: this is not intended for general use, it is mainly implemented to be used in uxbox and released as separated project for conveniendce. Don't expect compromise for backward compatibility.
Let's see an example of how to use rumext macros for define class based components.
(require '[rumext.alpha as mf])
(mf/def local-state
:desc "A component docstring/description (optional)."
:mixins [(mf/local 0)]
:init
(fn [own props]
(println "Component initialized")
own)
:render
(fn [own {:keys [title] :as props}]
(let [*count (::mf/local state)]
[:div {:on-click #(swap! *count inc)}
[:span title ": " @*count]])))
(mf/def parent-component
:render
(fn [own props]
[:section
[:h1 "Some title"]
[:& local-state {:title "Some title"}]]))
This example uses the mf/local
mixin that provides a local mutable stat
to the component.
You need to use the mf/reactive
mixin and mf/react
(instead of
deref) for deref the atom. Let's see an example:
(def count (atom 0))
(mf/def counter
:mixins [mf/reactive]
:render
(fn [own props]
[:div {:on-click #(swap! count inc)}
[:span "Clicks: " (mf/react count)]]))
(mf/mount (mf/element counter) js/document.body)
If you have a component that only accepts immutable data structures,
you can use the mf/memo
mixin for avoid unnecesary renders if
arguments does not change between them.
(mf/def title
:mixins [mf/memo]
:render
(fn [_ {:keys [name]}]
[:div {:class "label"} name]))
So if we manuall trigger the component mounting, we will obtain:
(mf/mount (mf/element title {:name "ciri"}) body) ;; first render
(mf/mount (mf/element title {:name "ciri"}) body) ;; second render: don't be rendered
(mf/mount (mf/element title {:name "geralt"}) body) ;; third render: re-render
(mf/mount (mf/element title {:name "geralt"}) body) ;; forth render: don't be rendered
The mf/memo
mixin uses indentical?
for compare props. If you want
equality by value (using the =
function), you can use mf/pure
mixin.
There also mf/static
mixin that completelly prevents rerendering.
Here’s a full list of lifecycle methods supported by rumext:
:init ;; state, props => state (called once, on component constructor)
:did-catch ;; state, err, inf => state (like try/catch for components)
:did-mount ;; state => state
:did-update ;; state, snapshot => state
:after-render ;; state => state (did-mount and did-update alias)
:should-update ;; old-state, state => bool (the shouldComponentUpdate)
:will-unmount ;; state => state
:derive-state ;; state => state (similar to legacy will-update and will-mount)
:make-snapshot ;; state => snapshot
A mixin is a map with a combination of this methods. And a component can have as many mixins as you need.
If you don't understand some methods, refer to react documentation: https://reactjs.org/docs/react-component.html
Function components as it's name says, are defined using plain
functions. Rumext exposes a lightweigh macro over a fn
that convert
props from js-object to cljs map (shallow) and exposes a facility for
docorate (wrap) with other higher-order components.
Let's see a example of how to define a component:
(require '[rumext.alpha :as mf])
(mf/defc title
[{:keys [name]}]
[:div {:class "label"} name])
This is the way you have to extend/add additional functionality to a function component. Rumext exposes two:
mf/wrap-memo
: same functionality as mf/memo
in class based components.In order to use the high-order components, you need wrap the component manually or passing it as a special property in the metadata:
(mf/defc title
{::mf/wrap [mf/wrap-memo]}
[props]
[:div {:class "label"} (:name props)])
React hooks is a basic primitive that React exposes for add state and side-effects to functional components. Rumext exposes right now only three hooks with a ClojureScript based api.
Hook used for maintain a local state and in functional components
replaces the mf/local
mixin. Calling mf/use-state
returns an
atom-like object that will deref to the current value and you can call
swap!
and reset!
on it for modify its state.
Any mutation will schedule the component to be rerendered.
(require '[rumext.alpha as mf])
(mf/defc local-state
[props]
(let [local (mf/use-state 0)]
[:div {:on-click #(swap! local inc)}
[:span "Clicks: " @local]]))
(mf/mount (mf/element local-state) js/document.body)
In the same way as use-state
returns an atom like object. The unique
difference is that updating the ref value does not schedules the
component to rerender.
This is a primitive that allows incorporate probably efectful code into a functional component:
(mf/defc local-timer
[props]
(let [local (mf/use-state 0)]
(mf/use-effect
{:fn (fn []
(let [sem (js/setInterval #(swap! local inc) 1000)]
#(js/clearInterval sem)))})
[:div "Counter: " @local]))
(mf/mount (mf/element local-state) js/document.body)
The :fn
callback will be called once the component mounts (like
did-mount
) and the returned function callback will be caled once the
component will unmounts (like will-unmount
). Returning function is
optional.
There are an excepction when you pass a :deps
parameter, that can
change how many times :fn
will be executed:
:deps nil
the default behavior explained before.:deps true
the :fn
callback will be executed after each render
(a combination of did-mount
and did-update
class based
components).:deps (mf/deps variable1 variable2)
: only execute :start
when some of
referenced variables changes.:watch []
the same as :watch nil
.NOTE: for avoid vector to js-array conversion, you can pass directly a
js array in :deps
.
The purpose of this hook is return a memoized value.
Example:
(mf/defc sample-component
[{:keys [x]}]
(let [v (mf/use-memo {:fn #(pow x 10)
:deps (mf/deps x)})]
[:span "Value is:" v]))
On each render, while x
has the same value, the v
only will be
calculated once.
This is a custom hook, alternative to mf/reactive
mixin on class components.
The purpose of this hook is reactivelly rerender component on a reference (atom-like object) changes.
Example:
(def clock (atom (.getTime (js/Date.))))
(js/setInterval #(reset! clock (.getTime (js/Date.))) 160)
(mf/defc timer
[props]
(let [ts (mf/deref clock)]
[:div "Timer (deref)" ": "
[:span ts]]))
In some circumstances you will want access to the raw react hooks
functions. For this purpose, rumext exposes the following functions:
useState
, useRef
, useMemo
and useEffect
.
Licensed under Eclipse Public License (see LICENSE).
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close