Reagent React Wrapper Components

Table of Contents

1. Overview

Reagent provides a minimalist interface for building React components in ClojureScript, while Re-frame adds an application architecture layer for managing state and side effects.

2. Background

verified · pilot-annotation-pass · 2026-10-08

Reagent was created by Dan Holmsand in 2013 as a simple way to use React from ClojureScript, representing components as plain functions returning Hiccup-style vectors. Re-frame, developed by Mike Thompson starting in 2015, builds on Reagent to provide a unidirectional data flow architecture inspired by Elm and Redux.

3. Key Concepts

3.1. Reagent Component Forms

Three ways to define components with different lifecycle behaviors:

;; Form-1: Simple function (re-renders on any change)
(defn greeting [name]
  [:div "Hello, " name])

;; Form-2: Function returning function (closure for local state)
(defn counter []
  (let [count (reagent/atom 0)]
    (fn []
      [:div
       [:span @count]
       [:button {:on-click #(swap! count inc)} "+"]])))

;; Form-3: Full lifecycle (for React interop)
(defn canvas-component []
  (reagent/create-class
   {:component-did-mount (fn [this] (setup-canvas (reagent/dom-node this)))
    :reagent-render (fn [] [:canvas])}))

3.2. Re-frame Architecture

Event-driven state management with subscriptions and effects:

;; Event handler
(re-frame/reg-event-db
 :increment
 (fn [db [_ amount]]
   (update db :count + amount)))

;; Subscription
(re-frame/reg-sub
 :count
 (fn [db _]
   (:count db)))

;; Component using subscription
(defn counter-display []
  (let [count @(re-frame/subscribe [:count])]
    [:div "Count: " count]))

3.3. Wrapping React Libraries

corrected · pilot-annotation-pass · 2026-10-08

Adapt JavaScript React components for Reagent:

(def MaterialButton
  (reagent/adapt-react-class
   (.-Button (js/require "@material-ui/core"))))

(defn my-button []
  [MaterialButton {:variant "contained" :color "primary"}
   "Click Me"])

3.4. Component Composition Patterns

;; Higher-order component
(defn with-loading [component]
  (fn [props]
    (if (:loading props)
      [:div.spinner "Loading..."]
      [component props])))

;; Render props pattern
(defn mouse-tracker [render-fn]
  (let [pos (reagent/atom {:x 0 :y 0})]
    (fn []
      [:div {:on-mouse-move #(reset! pos {:x (.-clientX %)
                                          :y (.-clientY %)})}
       [render-fn @pos]])))

4. Implementation

4.1. Project Dependencies

corrected · pilot-annotation-pass · 2026-10-08
;; deps.edn
{:deps {reagent/reagent {:mvn/version "0.8.1"}
        re-frame/re-frame {:mvn/version "0.10.5"}}}

4.2. Application Bootstrap

(ns app.core
  (:require [reagent.dom :as rdom]
            [re-frame.core :as rf]))

(rf/reg-event-db :initialize
  (fn [_ _] {:count 0}))

(defn app []
  [:div
   [counter-display]
   [:button {:on-click #(rf/dispatch [:increment 1])} "+"]])

(defn init []
  (rf/dispatch-sync [:initialize])
  (rdom/render [app] (.getElementById js/document "app")))

4.3. Effect Handlers for Side Effects

(rf/reg-fx
 :http-xhrio
 (fn [{:keys [uri on-success on-failure]}]
   (-> (js/fetch uri)
       (.then #(.json %))
       (.then #(rf/dispatch (conj on-success %)))
       (.catch #(rf/dispatch (conj on-failure %))))))

5. References

6. Notes

  • Use reagent/track for derived computations that cache like subscriptions
  • Prefer Form-1 components unless you need local state or lifecycle methods
  • Re-frame subscriptions are automatically cached and deduplicated
  • Consider re-frame-http-fx for HTTP effects rather than custom implementations
  • Use re-frame-10x devtools for time-travel debugging in development

7. Related