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.
I am excited about this 👍🏻
@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
darn
any console messages?
I’ll look at it tomorrow. I’m done for today
no there's nothing unfortunately
Your other message was regarding this…you have it fixed, right?
no
I think this may have to do with dynamic routing, since the part that doesn't go blank is the shell
hm. does an additional render fix it? E.g. clicking on something?
yes
ah…ok, interesting. So the forced render is what breaks it
but the next render fixes it
that should not be too hard to correct.
nothing like breaking hot reload to remind ourselves how great it is to have it 😄
I don’t have the same breakage in my projects
hot code reload is working perfectly for me. I’m trying it in my largest project. Let me try it without deps overrides.
I’ll try react 19…works with 18
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.I used to use that as well in DOM inputs…but removed it.
are you using v9 of fluentui? https://github.com/microsoft/fluentui/blob/6ec387a4446cb72c54fd534c40af031abec4d24c/apps/public-docsite-v9/src/Concepts/Migration/FromV0/Components/Ref.stories.mdx#L7
semantic-ui-react…doesn’t support react 19 yet
and a lot of my react components from js land are also not up to react 19
but hot code reload worked fine for me on the fulcro-rad-demo
and with react 18 on this project
does the rad demo use dynamic routing?
it does
RAD uses dr
do you have any console errors on your white screen hot reload?
no. the whole top-chrome tree disappears
I mean, that is sort of how react behaves without error boundaries…do you have error boundary?
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 reloadbut if I go deeper into the navigation it's a white screen again
do you have error boundaries in your app?
my guess is that the white screen effect is an uncaught exception that is hitting inside of React on some hook/lifecycle
though I’m puzzled as to why you’re not getting a console error message
i tried with and without
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
and why it isn’t affecting fulcro-rad-demo
oh, but I can only use react 18 there as well…semantic ui react still
so it might be just react 19? Can you try react 18, or do you need 19?
i like react 19 errors reporting
interesting
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))I just had
(rad-app/install-ui-controls! app controls/all-controls)
(app/mount! app Root "app")yes
AH, so you were re-mounting…was that it? It is supposed to work, but I didn’t realize the diff
the refresh dyn queries is more advanced
yes it works now so the remounting is not working then
AH, ok. That makes sense actually…so what broke is doing hot code reload using mount. I’ll see if I can fix that
hm…mount works for me 😛
I’m working on a relatively trivial app though…the old fulcro template
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?
hm…I changed ALL my components to use-hooks, and seems ok
Try 3c1ce3b4f7fd192649d437631ac2d499cdfc9a38
(remount-bugfix branch)
I suspect that might fix it, since it just changes mount to force a render instead of trying to reconstruct things
it does fix it yes
but what makes more sense in refresh, (app/force-root-render! app) or (app/mount! app Root "app")?
book says mount!https://book.fulcrologic.com/#_application_source
right…I don’t even remember why 😄
I think it might even be necessary in case you change the root…
in fact of course it is
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.
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?
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
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
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?
If you save it as steps, then how do you deal with resumption such as the user, reloading the browser, losing network connectivity, etc
You can certainly leverage various rad elements... At the very least the field rendering and validation
But yeah it was me I'd leverage state charts
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
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
then the only detail is the validation in step 1 for the next button
> 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?
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
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)
how do I know if I rely on multi-root renderer?
You’ll get a compiler error
> The required namespace "com.fulcrologic.fulcro.rendering.keyframe-render2" is not available, it was required by "brian/application.cljc".
just removin keyframe-render2 and updating rad fixed it
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
Glad that fixed it.