Simple and Decomplected UI library based on React.
Add to deps.edn:
funcool/rumext {:mvn/version "2022.04.08-137"}
This project is originated as a friendly fork of rum for a personal use but it is evolved to be a completly independent library that right now does not depend on it. In any case, many thanks to Tonksy for creating rum.
This is the list of the main differences:
WARNING: this is not intended for general use, it is mainly implemented to be used in penpot and released as separated project for conveniendce. Don't expect compromise for backward compatibility.
Function components as it's name says, are defined using plain
functions. Rumext exposes a lighweigh 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])
If you don't want the props in cljs data structure, you can disable
the props conversion passing ::mf/wrap-props false
as metadata:
(require '[goog.object :as gobj])
(mf/defc title
[props]
(let [name (gobj/get props "name")]
[:div {:class "label"} name]))
You may be already familiar with hiccup syntax for defining the react dom. The intention on this section is explain only the essential part of it and the peculiarities of hiccada and rumext.
Lets start with simple generic components like :div
:
[:div {:class "foobar"
:style {:background-color "red"}
:on-click some-on-click-fn}
"Hello World"]
Until here, nothing new, looks like any hiccup template. The
As you can observe, looks very familiar. On default components the
props are transformed recursively at compile time to a js object
transforming all keys from kebab-case to camelCase (and rename
:class
to className
); so the result will look aproximatelly like
this in jsx:
const h = React.createElement;
h("div", {className: "foobar",
style: {"backgroundColor": "red"},
onClick=someFn},
"Hello World");
TODO
This is the way you have to extend/add additional functionality to a function component. Rumext exposes one:
mf/memo
: analogous to React.memo
, adds memoization to the
component based on props comparison.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)])
By default identical?
predicate is used for compare props; this is
how you can pass a custom compare function:
(mf/defc title
{::mf/wrap [#(mf/wrap-memo % =)]}
[props]
[:div {:class "label"} (:name props)])
If you want create a own high-order component you can use mf/fnc
macro:
(defn some-factory
[component param]
(mf/fnc myhighordercomponent
{::mf/wrap-props false}
[props]
[:section
[:> component 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. 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 []
(let [sem (js/setInterval #(swap! local inc) 1000)]
#(js/clearInterval sem))))
[:div "Counter: " @local]))
(mf/mount (mf/element local-state) js/document.body)
The use-effect
is a two arity function. If you pass a single
callback function it acts like there are no dependencies, so the
callback will be executed once per component (analgous to didMount
and willUnmount
).
If you want to pass dependencies you have two ways:
rumext.alpha/deps
helper(mf/use-effect
(mf/deps x y)
(fn [] (do-stuff x y)))
And finally, if you want to execute it on each render, pass nil
as
deps (much in the same way as raw useEffect works.
The purpose of this hook is return a memoized value.
Example:
(mf/defc sample-component
[{:keys [x]}]
(let [v (mf/use-memo (mf/deps x) #(pow x 10))]
[:span "Value is:" v]))
On each render, while x
has the same value, the v
only will be
calculated once.
There is also the rumext.alpha/use-callback
for a specific use
cases.
A custom hook that adds ractivity to atom changes to the component.
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
, useCallback
, useLayoutEffect
and
useEffect
.
mf/catch
high-order component.React.memo
: mf/memo'
.mf/element
and mf/create-element
.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