A fork of Fulcro RAD that replaces UI State Machines (UISM) and Dynamic Router with fulcrologic/statecharts for all form, report, container, and routing lifecycle management.
Fulcro RAD originally used UI State Machines (UISM) for form and report lifecycle and Dynamic Router for navigation. This worked, but had limitations:
-
UISMs are ad-hoc — they lack formal semantics, making complex state logic hard to reason about.
-
Dynamic Router couples routing to components — route segments are declared on components, mixing concerns.
-
Testing required a browser — CLJS-only code paths made headless testing difficult.
Statecharts provide formal state management with hierarchical states, guards, and actions. Combined with statechart-based routing, all lifecycle and navigation logic is testable from a plain Clojure REPL.
Attributes are unchanged from standard RAD:
(ns com.example.model.account
(:require
[com.fulcrologic.rad.attributes :as attr :refer [defattr]]
[com.fulcrologic.rad.attributes-options :as ao]))
(defattr id :account/id :uuid
{ao/identity? true
ao/schema :production})
(defattr name :account/name :string
{ao/identities #{:account/id}
ao/schema :production})defsc-form works the same as before. The macro generates statechart options automatically:
(ns com.example.ui.account-forms
(:require
[com.fulcrologic.rad.form :as form]
[com.fulcrologic.rad.form-options :as fo]
[com.example.model.account :as account]))
(form/defsc-form AccountForm [this props]
{fo/id account/id
fo/attributes [account/name]
fo/route-prefix "account"
fo/title "Edit Account"})defsc-report also works the same:
(ns com.example.ui.account-report
(:require
[com.fulcrologic.rad.report :as report]
[com.fulcrologic.rad.report-options :as ro]
[com.example.model.account :as account]))
(report/defsc-report AccountReport [this props]
{ro/title "Accounts"
ro/source-attribute :account/all-accounts
ro/row-pk account/id
ro/columns [account/name]
ro/route-prefix "accounts"})Instead of Dynamic Router, you declare a statechart that defines all routes:
(ns com.example.ui.ui
(:require
[com.fulcrologic.fulcro.components :as comp :refer [defsc]]
[com.fulcrologic.rad.routing :as rroute]
[com.fulcrologic.statecharts.chart :refer [statechart]]
[com.fulcrologic.statecharts.integration.fulcro.routing :as scr]))
;; A container component that renders the current route
(defsc Routes [this props]
{:query [:ui/placeholder]
:preserve-dynamic-query? true
:initial-state {}
:ident (fn [] [:component/id ::Routes])}
(scr/ui-current-subroute this comp/factory))
(def routing-chart
(statechart {:initial :state/route-root}
(scr/routing-regions
(scr/routes {:id :state/root :routing/root `Routes}
;; Landing page (plain route state)
(scr/rstate {:route/target `LandingPage})
;; Reports
(rroute/report-route-state {:route/target `AccountReport})
;; Forms (with route params for the entity id)
(rroute/form-route-state {:route/target `AccountForm
:route/params #{:account/id}})))))(ns com.example.system
(:require
[com.example.ui.ui :refer [routing-chart]]
[com.fulcrologic.rad.application :as rad-app]
[com.fulcrologic.rad.rendering.headless.plugin])) ;; or your UI plugin
(defn start! [app]
;; 1. Install statechart infrastructure
(rad-app/install-statecharts! app)
;; 2. Start routing with your chart
(rad-app/start-routing! app routing-chart)
;; 3. (CLJS only) Enable URL synchronization
#?(:cljs (rad-app/install-url-sync! app)))Use com.fulcrologic.rad.routing for all navigation:
(require '[com.fulcrologic.rad.routing :as rroute])
;; Navigate to a report
(rroute/route-to! this AccountReport)
;; Edit an existing entity
(rroute/edit! this AccountForm account-id)
;; Create a new entity
(rroute/create! this AccountForm)com.fulcrologic.rad.routing is the recommended namespace for RAD applications. It provides:
-
route-to!— Navigate to any route target (form, report, or plain component). -
edit!— Route to a form and start editing an existing entity. -
create!— Route to a form and start creating a new entity. -
back!/route-forward!— Browser history navigation. -
force-continue-routing!— Override a route-denied guard (e.g., unsaved changes). -
abandon-route-change!— Cancel a denied route change.
For advanced use, com.fulcrologic.statecharts.integration.fulcro.routing (scr/) provides the lower-level statecharts routing API. Most RAD applications should use rroute/ exclusively.
The old install-ui-controls! pattern has been replaced by multimethods. Rendering plugins register via defmethod on:
-
fr/render-field— Field rendering (by[type style]) -
fr/render-form— Form layout rendering -
rr/render-report— Report layout rendering -
form/render-element— Generic form element rendering (by[element style]) -
control/render-control— Control rendering (by[control-type style])
Simply requiring the plugin namespace is sufficient — no explicit install-ui-controls! call needed.
A headless rendering plugin is included at com.fulcrologic.rad.rendering.headless.plugin for testing.
A complete working demo is included in src/demo/. It shows:
-
Attribute definitions (
com.example.model.*) -
Form and report definitions (
com.example.ui.*) -
Routing chart with forms, reports, and a landing page
-
Bootstrap sequence with
install-statecharts!andstart-routing! -
Both CLJS (browser) and CLJ (headless test) entry points
If you are migrating from the original fulcro-rad, see the migration guide.
Questions, issue reports, or requests should go through the #fulcro channel of Clojurians Slack.
Paid support is available from Fulcrologic, LLC.
The MIT License (MIT) Copyright (c), Fulcrologic, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.