Liking cljdoc? Tell your friends :D

Migration Guide: fulcro-rad to fulcro-rad-statecharts

Overview

The core change is replacing two systems:

  1. UI State Machines (UISM) — replaced by fulcrologic/statecharts for form, report, and container lifecycle.

  2. 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.

Dependency Changes

;; 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"}}}

Namespace Changes

Removed Namespaces

The following namespaces have been removed entirely:

NamespaceReason

com.fulcrologic.rad.authorization

Auth/redaction framework — deferred to future work

com.fulcrologic.rad.blob, blob-storage

File upload/storage — deferred to future work

com.fulcrologic.rad.rad-hooks

Hook system — deferred to future work

com.fulcrologic.rad.routing.history, routing.html5-history, routing.base

Replaced by statechart routing with URL sync

com.fulcrologic.rad.pathom3, resolvers-pathom3

Pathom 3 resolver generation — not ported

com.fulcrologic.rad.ui-validation

Removed — use standard Fulcro form-state validation

com.fulcrologic.rad.dynamic.generator, generator-options

Dynamic form/report generation — removed

New Namespaces

NamespacePurpose

com.fulcrologic.rad.routing

RAD routing API (delegates to statechart routing)

com.fulcrologic.rad.form-chart

Form lifecycle statechart

com.fulcrologic.rad.form-expressions

Statechart expression functions for forms

com.fulcrologic.rad.form-machines

Reusable chart fragments for custom form statecharts

com.fulcrologic.rad.report-chart

Standard report lifecycle statechart

com.fulcrologic.rad.report-expressions

Statechart expression functions for reports

com.fulcrologic.rad.container-chart

Container lifecycle statechart

com.fulcrologic.rad.container-expressions

Statechart expression functions for containers

com.fulcrologic.rad.server-paginated-report

Server-paginated report statechart

com.fulcrologic.rad.incrementally-loaded-report

Incrementally-loaded report statechart

com.fulcrologic.rad.sc.session

Session ID utilities for forms/reports

com.fulcrologic.rad.rendering.headless.*

Headless rendering plugin for testing

Bootstrap Changes

Before (fulcro-rad)

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

After (fulcro-rad-statecharts)

(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).

Routing Changes

Before: Dynamic Router

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

After: Statechart Routing

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)

Route State Builders

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 (Unsaved Changes)

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

Routing API Reference

com.fulcrologic.rad.routing (rroute/) is the recommended API for RAD applications:

FunctionPurpose

rroute/route-to!

Navigate to any route target. Accepts (app target) or (app target data).

rroute/edit!

Route to a form and edit an existing entity by ID.

rroute/create!

Route to a form and create a new entity (generates a tempid).

rroute/back!

Navigate back in browser history.

rroute/route-forward!

Navigate forward in browser history.

rroute/force-continue-routing!

Override a route-denied guard.

rroute/abandon-route-change!

Cancel a denied route change.

rroute/route-denied?

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.

Form Changes

Macro

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.

Navigation to Forms

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)

on-change Trigger

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

Report Changes

Macro

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

Specialized Report Variants

  • com.fulcrologic.rad.server-paginated-report — For server-side pagination.

  • com.fulcrologic.rad.incrementally-loaded-report — For incremental loading patterns.

Rendering Plugin Changes

Before: Map-based controls

;; Old: a map of type->style->renderer
(def all-controls {...})
(rad-app/install-ui-controls! app all-controls)

After: Multimethods

;; 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.

Container Changes

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.

What’s Deferred

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

CLJC and Headless Testing

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

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close