;; deps.edn — replace fulcro-rad with fulcro-rad-statecharts
{:deps {com.fulcrologic/fulcro-rad-statecharts {:mvn/version "RELEASE"}
com.fulcrologic/fulcro {:mvn/version "3.9.3"}}}
This guide covers migrating from the original com.fulcrologic/fulcro-rad to com.fulcrologic/fulcro-rad-statecharts.
The core change is replacing two systems:
UI State Machines (UISM) — replaced by fulcrologic/statecharts for form, report, and container lifecycle.
Dynamic Router — replaced by statechart-based routing for all navigation.
The data model (attributes), form/report declaration macros (defsc-form, defsc-report, defsc-container), and server-side resolvers are largely unchanged. The main differences are in bootstrap, routing, and rendering plugin registration.
;; deps.edn — replace fulcro-rad with fulcro-rad-statecharts
{:deps {com.fulcrologic/fulcro-rad-statecharts {:mvn/version "RELEASE"}
com.fulcrologic/fulcro {:mvn/version "3.9.3"}}}
The following namespaces have been removed entirely:
| Namespace | Reason |
|---|---|
| Auth/redaction framework — deferred to future work |
| File upload/storage — deferred to future work |
| Hook system — deferred to future work |
| Replaced by statechart routing with URL sync |
| Pathom 3 resolver generation — not ported |
| Removed — use standard Fulcro form-state validation |
| Dynamic form/report generation — removed |
| Namespace | Purpose |
|---|---|
| RAD routing API (delegates to statechart routing) |
| Form lifecycle statechart |
| Statechart expression functions for forms |
| Reusable chart fragments for custom form statecharts |
| Standard report lifecycle statechart |
| Statechart expression functions for reports |
| Container lifecycle statechart |
| Statechart expression functions for containers |
| Server-paginated report statechart |
| Incrementally-loaded report statechart |
| Session ID utilities for forms/reports |
| Headless rendering plugin for testing |
(ns com.example.app
(:require
[com.fulcrologic.rad.application :as rad-app]
[com.example.ui.semantic-ui-controls :as sui]))
(defn setup! [app]
(rad-app/install-ui-controls! app sui/all-controls)
;; Dynamic Router handled routing implicitly via will-enter/route-segment
)
(ns com.example.app
(:require
[com.fulcrologic.rad.application :as rad-app]
[com.fulcrologic.rad.rendering.headless.plugin] ;; or your UI plugin
[com.example.ui.ui :refer [routing-chart]]))
(defn setup! [app]
;; 1. Requiring the plugin namespace registers renderers via multimethods
;; 2. Install statechart infrastructure
(rad-app/install-statecharts! app)
;; 3. Start routing with your statechart
(rad-app/start-routing! app routing-chart)
;; 4. (CLJS only) URL synchronization
#?(:cljs (rad-app/install-url-sync! app)))
Key differences:
install-ui-controls! is replaced by requiring a plugin namespace (multimethods register on load).
install-statecharts! installs the statechart processor on the Fulcro app.
start-routing! starts the routing statechart (you define the chart explicitly).
install-url-sync! enables browser URL synchronization (CLJS only).
With Dynamic Router, routes were declared on components:
;; Old: route-segment on the component
(form/defsc-form AccountForm [this props]
{fo/id account/id
fo/attributes [account/name]
fo/route-prefix "account"
;; These were generated by defsc-form:
;; :route-segment ["account" :account/id]
;; :will-enter (fn [...] ...)
})
;; Old: defrouter
(dr/defrouter MainRouter [this props]
{:router-targets [AccountForm InventoryReport ...]})
;; Old: navigation
(dr/change-route! this ["account" (str account-id)])
Routes are declared in a central statechart. Components no longer have :route-segment or :will-enter:
(require '[com.fulcrologic.rad.routing :as rroute])
(require '[com.fulcrologic.statecharts.chart :refer [statechart]])
(require '[com.fulcrologic.statecharts.integration.fulcro.routing :as scr])
;; Route container (replaces defrouter)
(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))
;; Routing chart (central route definition)
(def routing-chart
(statechart {:initial :state/route-root}
(scr/routing-regions
(scr/routes {:id :state/root :routing/root `Routes}
(scr/rstate {:route/target `LandingPage})
(rroute/report-route-state {:route/target `InventoryReport})
(rroute/form-route-state {:route/target `AccountForm
:route/params #{:account/id}})))))
;; Navigation
(rroute/route-to! this InventoryReport)
(rroute/edit! this AccountForm account-id)
(rroute/create! this AccountForm)
com.fulcrologic.rad.routing provides helpers for defining route states:
rroute/form-route-state — Creates a route state that starts the form statechart on entry and abandons it on exit.
rroute/report-route-state — Creates a route state that starts the report on entry.
scr/rstate — Creates a plain route state for non-RAD components (e.g., a landing page).
Route denial is handled by the statechart’s busy guard. Check for denial in your Root component:
(require '[com.fulcrologic.statecharts.integration.fulcro :as scf])
(require '[com.fulcrologic.statecharts.integration.fulcro.routing :as scr])
;; In Root render:
(let [routing-states (scf/current-configuration this scr/session-id)
route-denied? (contains? routing-states :routing-info/open)]
(when route-denied?
;; Show a dialog with "Continue" and "Cancel" buttons
(dom/button {:onClick #(rroute/force-continue-routing! this)} "Discard changes")
(dom/button {:onClick #(rroute/abandon-route-change! this)} "Cancel")))
com.fulcrologic.rad.routing (rroute/) is the recommended API for RAD applications:
| Function | Purpose |
|---|---|
| Navigate to any route target. Accepts |
| Route to a form and edit an existing entity by ID. |
| Route to a form and create a new entity (generates a tempid). |
| Navigate back in browser history. |
| Navigate forward in browser history. |
| Override a route-denied guard. |
| Cancel a denied route change. |
| Returns true if a route change is currently denied. |
For advanced use, com.fulcrologic.statecharts.integration.fulcro.routing (scr/) provides the lower-level statechart routing primitives. Most RAD applications should use rroute/ exclusively and avoid mixing both in the same codebase.
defsc-form is unchanged in usage. The macro now generates statechart options instead of Dynamic Router options:
:route-segment, :will-enter, :will-leave, :allow-route-change? are no longer generated.
sfro/statechart, sfro/busy?, sfro/initialize are generated automatically.
Custom statecharts can be provided via fo/statechart.
The old form/edit!, form/view!, form/create! functions are deprecated. Use the routing namespace instead:
;; Old
(form/edit! this AccountForm account-id)
(form/create! this AccountForm)
;; New
(rroute/edit! this AccountForm account-id)
(rroute/create! this AccountForm)
The fo/on-change callback signature changed:
;; Old: returns UISM env
(fn [uism-env form-ident key old-value new-value] uism-env)
;; New: returns a vector of statechart operations
(fn [env data form-ident key old-value new-value]
[(fops/apply-action (fn [s] (assoc-in s [...] new-value)))])
defsc-report is unchanged in usage. Like forms, the macro generates statechart options automatically.
ro/statechart is the new option for custom statecharts (replaces ro/machine).
com.fulcrologic.rad.server-paginated-report — For server-side pagination.
com.fulcrologic.rad.incrementally-loaded-report — For incremental loading patterns.
;; Old: a map of type->style->renderer
(def all-controls {...})
(rad-app/install-ui-controls! app all-controls)
;; New: defmethod registrations (in plugin namespace)
(defmethod fr/render-field [:string :default] [env attr] ...)
(defmethod fr/render-form :default [env form-instance props] ...)
(defmethod rr/render-report :default [env report-instance props] ...)
Simply requiring the plugin namespace registers all renderers. No explicit installation step needed.
A headless plugin is included for testing: com.fulcrologic.rad.rendering.headless.plugin.
defsc-container works the same, but uses a statechart internally. Child reports are started via report/start-report! on entry. The container broadcasts events to children for coordination.
The following features from the original fulcro-rad are not yet ported:
Authorization — The auth/redaction framework (authorization.cljc).
Blob/File Upload — File upload and storage (blob.cljc, blob-storage.cljc).
Hooks — The RAD hooks system (rad-hooks.cljc).
Pathom 3 — Pathom 3 resolver generation (Pathom 2 resolvers work as before).
All source is CLJC. You can run headless tests against the full application lifecycle from a Clojure REPL:
;; Build a headless test app
(require '[com.fulcrologic.fulcro.headless :as h])
(let [app (h/build-test-app {:root-class Root
:remotes {:remote (net/fulcro-http-remote ...)}})]
(app/mount! app Root :app)
(rad-app/install-statecharts! app {:event-loop? :immediate})
(rad-app/start-routing! app routing-chart))
The :event-loop? :immediate option processes statechart events synchronously, making tests deterministic without async waits.
See the demo app’s com.example.client/init for a complete example of both browser and headless entry points.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |