SkillAgentSearch skills...

Rehook

ClojureScript React library enabling data-driven architecture

Install / Use

/learn @wavejumper/Rehook
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

rehook

Clojars Project CircleCI

Clojurescript React library enabling data-driven architecture

About

rehook is built from small, modular blocks - each with an explicit notion of time, and a data-first design.

The core library does two things:

  • marry React hooks with Clojure atoms
  • avoids singleton state

Its modular design, and guiding philosophy has already enabled some rich tooling like rehook-test.

Example apps

Installation

The documentation assumes you are using shadow-cljs.

You will need to provide your own React dependencies, eg:

npm install --save react
npm install --save react-dom

Libraries

  • rehook/core - base state/effects fns
  • rehook/dom - hiccup templating DSL
  • rehook/test - test library

To include one of the above libraries, add the following to your dependencies:

[rehook/core "2.1.11"]

To include all of them:

[rehook "2.1.11"]

Usage

rehook.core

If you need a primer on React hooks, the API docs are a good start.

rehook.core exposes 5 useful functions for state and effects:

  • use-state convenient wrapper over react/useState
  • use-effect convenient wrapper over react/useEffect
  • use-atom use a Clojure atom (eg, for global app state) within a component
  • use-atom-path like use-atom, except for a path into a atom (eg, get-in)
  • use-atom-fn provide custom getter/setter fns to build your own abstractions

Usage

(ns demo
  (:require
    [rehook.core :as rehook]
    [rehook.dom :refer-macros [defui]]
    [rehook.dom.browser :as dom.browser]
    ["react-dom" :as react-dom]))

(defn system [] ;; <-- system map (this could be integrant, component, etc)
  {:state (atom {:missiles-fired? false})})

(defui my-component
  [{:keys [state]} ;; <-- context map from bootstrap fn
   props] ;; <-- any props passed from parent component
  (let [[curr-state _]                       (rehook/use-atom state) ;; <-- capture the current value of the atom
        [debug set-debug]                    (rehook/use-state false) ;; <-- local state
        [missiles-fired? set-missiles-fired] (rehook/use-atom-path state [:missiles-fired?])] ;; <-- capture current value of path in atom

    (rehook/use-effect
      (fn []
        (js/console.log (str "Debug set to " debug)) ;; <-- the side-effect invoked after the component mounts and debug's value changes
        (constantly nil)) ;; <-- the side-effect to be invoked when the component unmounts
      [debug])

    [:section {}
      [:div {}
        (if debug
          [:span {:onClick #(set-debug false)} "Hide debug"]
          [:span {:onClick #(set-debug true)} "Show debug"])
        (when debug
          (pr-str curr-state))]

      (if missiles-fired?
        [:div {} "Missiles have been fired!"]
        [:div {:onClick #(set-missiles-fired true)} "Fire missiles"])]))

;; How to render a component to the DOM
(react-dom/render
  (dom.browser/bootstrap
    (system) ;; <-- context map
    identity ;; <-- context transformer
    clj->js ;; <-- props transformer
    my-component) ;; <-- root component
  (js/document.getElementById "myapp"))

Hooks gotchas

  • When using use-effect, make sure the values of deps pass JavaScript's notion of equality! Solution: use simple values instead of complex maps.
  • Enforced via convention, React hooks and effects need to be defined at the top-level of your component (and not bound conditionally)

Components

rehook.dom

rehook.dom provides hiccup syntax.

rehook.dom provides a baggage free way to pass down application context (eg, integrant or component) as you will see below.

defui

rehook.dom/defui is a macro used to define rehook components. This macro is only syntactic sugar, as all rehook components are cljs fns.

defui takes in two arguments:

  • context: immutable, application context
  • props: any props passed to the component

It must return valid hiccup.

(ns demo
  (:require [rehook.dom :refer-macros [defui]]))

(defui my-component [{:keys [dispatch]} _]
  [:text {:onClick #(dispatch :fire-missiles)} "Fire missiles!"])

The anonymous counterpart is rehook.dom/ui

fragments

Use the :<> shorthand:

(defui fragmented-ui [_ _]
  [:<> {} [:div {} "Div 1"] [:div {} "Div 2"]])

rehook components

Reference the component directly:

(defui child [_ _]
  [:div {} "I am the child"])

(defui parent [_ _]
  [child])

ReactJS components

Same as rehook components. Reference the component directly:

(require '["react-select" :as ReactSelect])

(defui select [_ props]
  [ReactSelect props])

reagent components

(require '[reagent.core :as r])

(defn my-reagent-component []
  [:div {} "I am a reagent component, I guess..."])

(defui my-rehook-component [_ _]
  [(r/reactify-component my-reagent-component)])

children

;; acceptable
[:div {}
  (for [item items]
    [item {}])]

;; also acceptable
[:div {}
  [child1]
  [child2]]

Working with children in rehook components

(require '[rehook.util :as util])

(defui parent [_ props]
  [:div {} 
    (for [child (util/child-seq props)]
      [child {:onClick #(js/alert "Extra props merged into child!")}])])

...

[parent {} 
  [:div {:style {:color "pink"}} "I am a child"]]

hiccup-free

You can opt-out of hiuccup templating by passing a third argument (the render fn) to defui:

(defui no-html-macro [_ _ $]
  ($ :div {} "rehook-dom without hiccup!"))

Because the $ render fn is passed into every rehook component you can overload it -- or better yet create your own custom templating syntax!

Props

A props transformation fn is passed to the initial bootstrap fn. The return value of this fn must be a JS object.

A good default to use is cljs.core/clj->js.

If you want to maintain Clojure idioms, a library like camel-snake-kebab could be used to convert keys in your props (eg, on-press to onPress)

Props transformation is used for interop with vanilla React components. Therefore, all props passed into rehook do not go through the transformation fn, and remain untouched.

If you need to access the React props in Rehook components (for example, to access children), the JS props computed by React are available as metadata on the props map, under the :react/props key.

You can use the util fn rehook.util/react-props to conveniently extract the React props.

Initializing

react-dom

You can call react-dom/render directly, and bootstrap your component:

(ns example.core
  (:require
    [example.components :refer [app]]
    [rehook.dom.browser :as dom]
    ["react-dom" :as react-dom]))

(defn system []
  {:dispatch (fn [& _] (js/console.log "TODO: implement dispatch fn..."))})

(defn main []
  (react-dom/render
    (dom/bootstrap (system) identity clj->js app)
    (js/document.getElementById "app")))

react-native

You can use the rehook.dom.native/component-provider fn if you directly call AppRegistry

(ns example.core
  (:require
    [rehook.dom :refer-macros [defui]]
    [rehook.dom.native :as dom]
    ["react-native" :refer [AppRegistry]]))

(defui app [{:keys [dispatch]} _]
  [:Text {:onPress #(dispatch :fire-missiles)} "Fire missiles!"])

(defn system []
  {:dispatch (fn [& _] (js/console.log "TODO: implement dispatch fn..."))})

(defn main []
  (.registerComponent AppRegistry "my-app" (dom/component-provider (system) app)))

Alternatively, if you don't have access to the AppRegistry, you can use the rehook.dom.native/bootstrap fn instead - which will return a valid React element

Context transformer

The context transformer can be incredibly useful for instrumentation, or for adding additional abstractions on top of the library (eg implementing your own data flow engine ala domino)

For example:

(require '[rehook.util :as util])

(defn ctx-transformer [ctx component]
  (update ctx :log-ctx #(conj (or % []) (util/display-name component))))

(dom/component-provider (system) ctx-transformer clj->js app)

In this example, each component will have the hierarchy of its parents in the DOM tree under the key :log-ctx.

This can be incredibly useful context to pass to your logging/metrics library!

Linting / editor integration

cursive

  • rehook.dom/defui -- resolve as defn, indentation as indent
  • rehook.dom/ui -- resolve as fn, indentation as indent
  • rehook.test/defuitest -- resolve as defn, indentation as indent
  • rehook.test/initial-render -- indentation as 1
  • rehook.test/next-render -- indentation as 1
  • rehook.test/io -- indentation as 1
  • rehook.test/is -- indentation as 1

cljfmt

Add this to your cljfmt config:

{rehook.dom/defui [[:inner 0]]
 rehook.dom/ui    [[:inner 0]]}

clj-kondo (calva/etc)

Add this to your .clj-kondo/config.edn file:

{:lint-as {rehook.dom/defui clojure.core/defn
           rehook.dom/ui    clojure.core/fn}}

Testing

rehook allows you to test your entire application - from data layer to view.

Ho

View on GitHub
GitHub Stars77
CategoryDevelopment
Updated5mo ago
Forks2

Languages

Clojure

Security Score

97/100

Audited on Oct 11, 2025

No findings