Reagent React Wrapper Components

Table of Contents

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. Together, they form the foundation for building complex single-page applications with predictable data flow. This document covers component patterns, state management strategies, and integration techniques for wrapping existing React component libraries.

Background

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. The combination has become the dominant approach for ClojureScript frontend development.

Key Concepts

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])}))

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]))

Wrapping React Libraries

Adapt JavaScript React components for Reagent:

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

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

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]])))

Implementation

Project Dependencies

;; deps.edn
{:deps {reagent/reagent {:mvn/version "1.2.0"}
        re-frame/re-frame {:mvn/version "1.3.0"}}}

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

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 %))))))

References

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

Author: Jason Walsh

j@wal.sh

Last Updated: 2026-01-11 11:00:20

build: 2026-01-11 18:31 | sha: eb805a8