fulcro

tony.kay 2025-09-03T08:13:39.936869Z

Update on the state of React/Fulcro. I’ve been doing various patches to Fulcro to support React 19+ in an attempt to keep up with the js ecosystem, so that we can easily continue to leverage things in the react ecossytem (e.g. date pickers, dropdown controls, etc.). React 18+ has “concurrent rendering” that sort of breaks Fulcro a bit. React 17- did rendering synchronously…you told it when you needed a refresh, and it did the refresh. This allowed Fulcro’s tx processing system to schedule renders at very specific times (at tx boundaries) with a guarantee that the rendering would be complete after the call to render….tx -> render -> tx … But react 18+ breaks this. They schedule rendering asynchronously, which leads to the potential of a render failing to show data from a transaction, leading to all sorts of hard-to-diagnose bugs (esp with respect to dyn routing and forms) since the UI may have out-of-date props compared to the actual app state 😞 Worse, since Fulcro debounces rendering to a minimum number of renders (assuming that as long as the “most recent” is rendered) the fact that FUlcro cannot trust react to render synchronously means that it’s possible to “miss” a render that is significant (e.g. the view props end up being stale). Fulcro 3.9.0-rc8 is the current release I’m working on to address this, and I’m reworking the internals of Fulcro to leverage the new React hook useSyncExternalStore which is intended to help with this. Other user reports with bugs related to this have so far reported that this new internal change fixes their problems. The other unfortunate side-effect of this change is that the “pluggable rendering optimizations” of Fulcro are no longer really “functional” and I’ll need to analyze things to see where I can get some of those bits “back”, and also we may be leveraging things like multi-root-rendering, which is deprecated in Fulcro, and with 3.9 will almost certainly stop working correctly altogether. I try my best to never make breaking changes in Fulcro, but when the underlying ecosystem makes a breaking change, sometimes I’m forced to. This is going to be one of those cases. Fortunately, all of the potential breakage is on things I deprecated a long time ago, and noted the alternatives of. I will be removing ident-optimize, and multi-root renderers. In fact, pluggable rendering optimizations will really be limited to CLJ with non-react rendering. The main breakage will be in the use of floating roots (from multi-root-renderer). RAD uses one of these, and I have some legacy code in my own project that uses them as well. Fortunately, use-component is a pure hooks-based solution that is a better option, and it should be pretty easy to port code from floating roots to hooks. So for those using RAD, you’re going to need to update all of the dependencies together to get a working ecosystem.

danieroux 2025-09-03T08:38:21.658589Z

I am excited about this 👍🏻

Eric Dvorsak 2025-09-03T14:18:35.119059Z

@tony.kay One thing I noticed is that the hot reloading gives me a white screen. It's related to the most recent changes since it still works when I simply go back to ab9df27e88b51b2544c67a4ae9386665effd7437

tony.kay 2025-09-03T14:20:11.062959Z

darn

tony.kay 2025-09-03T14:20:49.513579Z

any console messages?

tony.kay 2025-09-03T14:20:57.769209Z

I’ll look at it tomorrow. I’m done for today

Eric Dvorsak 2025-09-03T14:22:51.425749Z

no there's nothing unfortunately

tony.kay 2025-09-03T14:34:27.093789Z

Your other message was regarding this…you have it fixed, right?

Eric Dvorsak 2025-09-03T14:34:54.954479Z

no

Eric Dvorsak 2025-09-03T14:35:25.985009Z

I think this may have to do with dynamic routing, since the part that doesn't go blank is the shell

tony.kay 2025-09-03T14:35:55.962269Z

hm. does an additional render fix it? E.g. clicking on something?

Eric Dvorsak 2025-09-03T14:36:09.474069Z

yes

tony.kay 2025-09-03T14:37:03.803599Z

ah…ok, interesting. So the forced render is what breaks it

tony.kay 2025-09-03T14:37:12.108909Z

but the next render fixes it

tony.kay 2025-09-03T14:37:22.364999Z

that should not be too hard to correct.

Eric Dvorsak 2025-09-03T14:44:07.362879Z

nothing like breaking hot reload to remind ourselves how great it is to have it 😄

tony.kay 2025-09-04T06:45:37.888489Z

I don’t have the same breakage in my projects

tony.kay 2025-09-04T06:46:07.216659Z

hot code reload is working perfectly for me. I’m trying it in my largest project. Let me try it without deps overrides.

tony.kay 2025-09-04T06:48:17.529209Z

I’ll try react 19…works with 18

tony.kay 2025-09-04T07:06:33.577499Z

Unfortunately, my project crashes on React 19 because some of my deps still use findDOMNode which was removed in react 19…:

./cljs-runtime/module$node_modules$react_smooth$node_modules$react_transition_group$cjs$Transition.js
./cljs-runtime/module$node_modules$react_dom$cjs$react_dom_client_development.js
./cljs-runtime/module$node_modules$react_smooth$node_modules$react_transition_group$cjs$Transition.js.map
./cljs-runtime/module$node_modules$react_smooth$node_modules$react_transition_group$cjs$ReplaceTransition.js.map
./cljs-runtime/module$node_modules$react_date_picker$node_modules$react_fit$dist$cjs$Fit.js
./cljs-runtime/module$node_modules$react_dom$cjs$react_dom_development.js
./cljs-runtime/module$node_modules$react_transition_group$Transition.js
./cljs-runtime/module$node_modules$semantic_ui_react$node_modules$$fluentui$react_component_ref$dist$commonjs$RefFindNode.js
./cljs-runtime/module$node_modules$react_dom$cjs$react_dom_development.js.map
./cljs-runtime/module$node_modules$react_dom$cjs$react_dom_client_development.js.map
./cljs-runtime/module$node_modules$react_date_picker$node_modules$react_fit$dist$cjs$Fit.js.map
./cljs-runtime/module$node_modules$react_smooth$node_modules$react_transition_group$cjs$ReplaceTransition.js
./cljs-runtime/module$node_modules$react_transition_group$Transition.js.map
./cljs-runtime/module$node_modules$semantic_ui_react$node_modules$$fluentui$react_component_ref$dist$commonjs$RefFindNode.js.map
these end up with console errors that hit my React error boundary and leave parts of the screen white.

tony.kay 2025-09-04T07:06:48.822499Z

I used to use that as well in DOM inputs…but removed it.

tony.kay 2025-09-04T07:10:47.880069Z

semantic-ui-react…doesn’t support react 19 yet

tony.kay 2025-09-04T07:11:34.098239Z

and a lot of my react components from js land are also not up to react 19

tony.kay 2025-09-04T07:11:49.775769Z

but hot code reload worked fine for me on the fulcro-rad-demo

tony.kay 2025-09-04T07:11:57.163819Z

and with react 18 on this project

Eric Dvorsak 2025-09-04T07:12:51.238369Z

does the rad demo use dynamic routing?

tony.kay 2025-09-04T07:14:44.093529Z

it does

tony.kay 2025-09-04T07:14:51.011629Z

RAD uses dr

tony.kay 2025-09-04T07:15:11.753669Z

do you have any console errors on your white screen hot reload?

Eric Dvorsak 2025-09-04T07:20:00.766339Z

no. the whole top-chrome tree disappears

tony.kay 2025-09-04T07:20:28.850889Z

I mean, that is sort of how react behaves without error boundaries…do you have error boundary?

Eric Dvorsak 2025-09-04T07:20:51.553229Z

Interestingly

(defsc TopChrome [_ {:root/keys [router]}]
  {:use-hooks? true
   :query [[:ui/webview? '_]
           {:ui/loading? [:ui/loading?]}
           {:root/router (comp/get-query TopRouter)}
           {[:root/current-session '_] [:user/id
                                        :user/role
                                        :user/nickname
                                        :user/profile-image
                                        :user/organization
                                        :user/paywall?
                                        :user/free-trial-days-remaining
                                        :subscription/active?]}
           {router-ident [::uism/active-state ::uism/local-storage]}]
   :ident (fn [] [:component/id :top-chrome])
   :initial-state {:root/router {}}}
  (ui-top-router router))

(def ui-top-chrome (comp/factory TopChrome))

(defn- top-chrome-wrapped
  [props]
  (let [loading? (#{:pending :deferred :state/loading} (get-in props [router-ident ::uism/active-state]))
        current-route (first (get-in props [router-ident ::uism/local-storage :path-segment]))]
    (dom/div
     {:id "top-chrome-wrapped"
      :className "my-9 relative rounded-3xl overflow-hidden w-full"}
     (cond
       loading? (cl/brian-loader)
       (#{"your-classes" "admin-panel" "directory"} current-route) (ui-top-chrome props)
       :else (dom/div
              {:className "bg-white py-9 w-full h-full"}
              (dom/div
               {:classes ["flex flex-col w-full overflow-y-auto"
                          (if (#{"user-settings"} current-route)
                            "h-[calc(100%-1.5rem)]"
                            "h-full")]}
               (dom/div
                {:className "w-full h-fit px-7 flex-grow"}
                (ui-top-chrome props))))))))
if I use the ui-top-chrome instead of the wrapper, the first screen survives hot reload

Eric Dvorsak 2025-09-04T07:21:12.385029Z

but if I go deeper into the navigation it's a white screen again

tony.kay 2025-09-04T07:22:02.816719Z

do you have error boundaries in your app?

tony.kay 2025-09-04T07:22:26.744089Z

https://book.fulcrologic.com/#_react_errors

tony.kay 2025-09-04T07:24:06.499479Z

my guess is that the white screen effect is an uncaught exception that is hitting inside of React on some hook/lifecycle

tony.kay 2025-09-04T07:24:21.658759Z

though I’m puzzled as to why you’re not getting a console error message

Eric Dvorsak 2025-09-04T07:26:06.662159Z

i tried with and without

tony.kay 2025-09-04T07:28:52.895539Z

So I did, in the internals, remove a bit of code that sets the type of the component when using hooks. I did that because using memo needs to set the type. Maybe that’s it? Maybe I need to set the type field. Still I don’t understand why that would only affect hot reload

tony.kay 2025-09-04T07:29:12.088139Z

and why it isn’t affecting fulcro-rad-demo

tony.kay 2025-09-04T07:31:15.508189Z

oh, but I can only use react 18 there as well…semantic ui react still

tony.kay 2025-09-04T07:32:04.075029Z

so it might be just react 19? Can you try react 18, or do you need 19?

Eric Dvorsak 2025-09-04T07:35:16.792749Z

i like react 19 errors reporting

Eric Dvorsak 2025-09-04T07:35:35.266809Z

interesting

Eric Dvorsak 2025-09-04T07:35:41.151549Z

fulcro-rad-demo has:

(defn refresh []
  ;; hot code reload of installed controls
  (log/info "Reinstalling controls")
  (setup-RAD app)
  (comp/refresh-dynamic-queries! app)
  (app/force-root-render! app))

Eric Dvorsak 2025-09-04T07:36:09.152419Z

I just had

(rad-app/install-ui-controls! app controls/all-controls)
(app/mount! app Root "app")

tony.kay 2025-09-04T07:36:09.225629Z

yes

tony.kay 2025-09-04T07:36:47.610769Z

AH, so you were re-mounting…was that it? It is supposed to work, but I didn’t realize the diff

tony.kay 2025-09-04T07:37:34.980219Z

the refresh dyn queries is more advanced

Eric Dvorsak 2025-09-04T07:38:30.542589Z

yes it works now so the remounting is not working then

tony.kay 2025-09-04T07:43:52.168299Z

AH, ok. That makes sense actually…so what broke is doing hot code reload using mount. I’ll see if I can fix that

tony.kay 2025-09-04T07:49:23.820559Z

hm…mount works for me 😛

tony.kay 2025-09-04T07:55:55.739889Z

I’m working on a relatively trivial app though…the old fulcro template

tony.kay 2025-09-04T08:02:03.957679Z

I added a hooks component, and that seems ok too. I wonder what you have going on that remounting hoses. Is your top-level component using hooks?

tony.kay 2025-09-04T08:02:50.298919Z

hm…I changed ALL my components to use-hooks, and seems ok

tony.kay 2025-09-04T08:17:23.157079Z

Try 3c1ce3b4f7fd192649d437631ac2d499cdfc9a38

tony.kay 2025-09-04T08:17:37.098599Z

(remount-bugfix branch)

tony.kay 2025-09-04T08:18:01.939989Z

I suspect that might fix it, since it just changes mount to force a render instead of trying to reconstruct things

Eric Dvorsak 2025-09-04T09:46:13.716389Z

it does fix it yes

Eric Dvorsak 2025-09-04T09:46:37.137139Z

but what makes more sense in refresh, (app/force-root-render! app) or (app/mount! app Root "app")?

Eric Dvorsak 2025-09-04T09:47:30.524629Z

book says mount!https://book.fulcrologic.com/#_application_source

tony.kay 2025-09-04T10:13:06.985029Z

right…I don’t even remember why 😄

tony.kay 2025-09-04T10:13:27.002429Z

I think it might even be necessary in case you change the root…

tony.kay 2025-09-04T10:13:35.393919Z

in fact of course it is

tony.kay 2025-09-04T10:38:10.117549Z

but I guess having to reload the browser if you change root to a completely different component (which you pretty much never do) isn’t really that big of a deal.

Eric Dvorsak 2025-09-03T09:14:19.879369Z

What is the most sensible approach for a multistep form in fulcro (optionally fulcro-rad)? With RAD, would it make sense to make every step a form, then the final step would include all the fields from the previous form without rendering them?

tony.kay 2025-09-03T09:21:05.156079Z

So there are a number of ways... You can just control the rendering and add a ui field for tracking and update the uism for control. Is there a common root form entity for all the data? If not you might consider building something with State charts, where you can leverage an overall hierarchical State machine to control things. I have plans to make a state chart compatible subsystem for rad, but I haven't finished it yet

tony.kay 2025-09-03T09:22:07.920459Z

One of the key interesting points is that a single form save can use the normalized diff to include any amount of normalized form saved data, even if the form elements are not related. The normalized diff is just key by ident. The root key and ID are mainly used for what the save for mutation returned, not what it accepts

tony.kay 2025-09-03T09:22:37.554869Z

So additional questions include things like do you want to save each step or wait until the final step and save it as a clump?

tony.kay 2025-09-03T09:22:54.169629Z

If you save it as steps, then how do you deal with resumption such as the user, reloading the browser, losing network connectivity, etc

tony.kay 2025-09-03T09:27:20.540869Z

You can certainly leverage various rad elements... At the very least the field rendering and validation

tony.kay 2025-09-03T09:27:45.707569Z

But yeah it was me I'd leverage state charts

Eric Dvorsak 2025-09-04T09:37:16.430629Z

it's actually just a two step form, same entity even though second step is technically a subform (1-1 relationship being added). We save only on the second step, but validation should prevent moving to the next

Eric Dvorsak 2025-09-04T09:38:18.225389Z

in that regard it sounds like the first thing you propose, controlling the rendering and having a ui field to decide which view to show is the simplest

Eric Dvorsak 2025-09-04T09:41:03.018589Z

then the only detail is the validation in step 1 for the next button

Eric Dvorsak 2025-09-04T09:43:55.847079Z

> I have plans to make a state chart compatible subsystem for rad, but I haven't finished it yet @tony.kay what kind of plans, are you planning to switch from statemachines to statecharts?

tony.kay 2025-09-04T10:15:27.017409Z

see the integration stuff in the statecharts lib…yes, my intention is to add statecharts support to RAD, and have completely composable systems that use statecharts as the entire logic

tony.kay 2025-09-03T12:45:38.790409Z

Fulcro 3.9.0-rc1 released. Removed legacy plug-in renderers, and leverages the latest react hooks to properly integrate state management. NOTES: • Breaking change: If you rely on multi-root renderer, you will have to do a port: ◦ port use-fulcro-mount to hooks/use-component. Should be relatively trivial. • The :use-hooks? option on components now supports true OR the keyword :pure. The latter will leverage React memo to prevent component re-renders if props have not changed. Note that hooks embedded in such components will still cause renders. But this can dramatically reduce irrelevant renders. See React memo documentation. Changing this option always requires a browser reload to take effect. RAD libraries have been updated (ported the fulcro-mount of autocomplete, and stopped setting multi-root renderer)

🙌 1
🎉 1
Eric Dvorsak 2025-09-03T14:16:03.687029Z

how do I know if I rely on multi-root renderer?

tony.kay 2025-09-03T14:20:27.749849Z

You’ll get a compiler error

Eric Dvorsak 2025-09-03T14:24:04.525129Z

> The required namespace "com.fulcrologic.fulcro.rendering.keyframe-render2" is not available, it was required by "brian/application.cljc".

Eric Dvorsak 2025-09-03T14:27:48.720249Z

just removin keyframe-render2 and updating rad fixed it

tony.kay 2025-09-03T14:33:51.223629Z

yeah…I realized that those renderers were all going to no longer work with React concurrent rendering…but I think the newer react’s can actually cancel prior renders…so it make actually end up faster. I also added :pure for :use-hooks? which might be a booster

tony.kay 2025-09-03T14:34:09.103979Z

Glad that fixed it.