fulcro

Eric Dvorsak 2025-09-23T13:20:08.713549Z

seems like we are also affected by stale closures in fulcro? https://dmitripavlutin.com/react-hooks-stale-closures/ I've had this case with a use-form hook where my closure wasn't updated. timeframe-data here kept the initial value, when I grab it with (comp/props this) within the closure it works

Eric Dvorsak 2025-09-25T06:20:35.383159Z

@tony.kay are you aware of this?

tony.kay 2025-09-27T08:27:22.022149Z

yeah, it is certainly believable that the new inputs for react 18+ are misbehaving. That’s why the RC has been running so long. I don’t feel super safe rewriting something that was so well-tested and is also pretty difficult to get right.

tony.kay 2025-09-27T08:27:41.588589Z

I’ve been waiting for all the bugs with inputs to crop up. I thought we’d gotten them all

tony.kay 2025-09-27T08:28:10.712059Z

thanks for the minimal repro. I’ll try to get to it soon

tony.kay 2025-09-27T12:42:12.486709Z

try rc11. I rewrote how the DOM input works. It tests out for me now.

tony.kay 2025-09-27T19:17:58.339099Z

I think I was actually able to simplify it, and it seems to work for the cases I can think of to test it against.

tony.kay 2025-09-27T19:18:23.119779Z

I suspect part of the simplification was enabled by the fact that I got the top-level integration with react concurrent rendering right.

tony.kay 2025-09-25T08:13:57.740409Z

of course I am. That’s how closures work. Deal with it all the time all over the place in front and back end. Nothing specific to React hooks here. It can be a little annoying to track down when/why, but it’s actually quite a common thing people confuse themselves about.

Eric Dvorsak 2025-09-25T08:14:41.432779Z

but when not using hooks it doesn't happen as far as I know? the function just gets updated

tony.kay 2025-09-25T08:14:42.887029Z

Also remember that clj had vars, which help somewhat, but cljs does not. A lot of the values are just, well, values. So closing over them is much more common in the front-end

tony.kay 2025-09-25T08:18:10.903359Z

what is ui-drag-and-drop???

tony.kay 2025-09-25T08:19:28.106149Z

My guess is that it is somehow telling react not to re-render (so it can re-capture the onFileSelected fn), which is leaving it with the prior version.

tony.kay 2025-09-25T08:19:33.382049Z

useMemo?

tony.kay 2025-09-25T08:20:41.551609Z

but yes, if you specifically re-grab the timeframe data from props at the higher level (as you say works), then if doesn’t matter if the lambda is closed over, because the props on this are a mutable thing from one render to the next. That’s my best guess.

Eric Dvorsak 2025-09-25T08:22:00.358599Z

Eric Dvorsak 2025-09-25T08:22:34.804789Z

it's a component of ours

tony.kay 2025-09-25T08:23:09.446609Z

Personally this style of programming is not something I prefer unless the dynamism of the app actually requires it. Hooks are “convenient”, but I don’t think they are necessarily the best way to structure an app you want to reason about. You end up with all sorts of mess. I support them because they are sometimes quite necessary to avoid other complexities and are the lesser of two evils. But I can count the components that use hooks in my 250k line code base on my hands.

tony.kay 2025-09-25T08:25:27.069699Z

anyway, hooks just trigger re-renders of components in complex ways, but they don’t rewrite your code (like core async does). So, IF the hook triggered an update, THEN you got new props and your let ran. the onFileSelected would have be regenerated and closed over the new value, and the body would have been called. Thus my guess that this code isn’t the problem, but instead the closure happened in the drag and drop component

tony.kay 2025-09-25T08:26:45.467409Z

AH, your DnD component is query-less…and you’r e passing two args to it’s factory? But not using computed props?

Eric Dvorsak 2025-09-25T08:27:01.757159Z

yes I guess that's what I was saying, that hooks mixed with closure seem to have the unexpected effect of staling values

tony.kay 2025-09-25T08:27:25.442959Z

hooks really don’t figure into this case AFAICT

Eric Dvorsak 2025-09-25T08:27:38.274749Z

> AH, your DnD component is query-less…and you’r e passing two args to it’s factory? But not using computed props? I thought that was the issue too but I tried with computed props as well, same staling issue

tony.kay 2025-09-25T08:28:03.860889Z

put a println at the top of your DnD component, and put one at the top of your hooks-based component. See if the child ALWAYS re-renders when the parent does.

tony.kay 2025-09-25T08:28:54.700319Z

the only way I see this going wrong is that your parent is re-rendering, but your factory is preventing the child (e.g. :shouldComponentUpdate is returning false in the child)

tony.kay 2025-09-25T08:29:06.294409Z

Try adding :shouldComponentUpdate true to the child

tony.kay 2025-09-25T08:29:42.122609Z

or you otherwise made some weird custom factory for the child that is causing the skip of render

tony.kay 2025-09-25T08:30:27.749969Z

it’s possible that when there is no query my pre-generated shouldComponentUpdate is malfunctioning

tony.kay 2025-09-25T08:32:01.768329Z

so you could also switch the child to hooks, which then stops using the classic component callbacks.

Eric Dvorsak 2025-09-25T08:46:14.905589Z

as mentioned I have a working solution already but happy to experiment, I was just sharing this in the hope we may find a way to fix/document this to make fulcro more consistent/user friendly

Eric Dvorsak 2025-09-25T08:54:10.681789Z

> Personally this style of programming is not something I prefer unless the dynamism of the app actually requires it. Hooks are “convenient”, but I don’t think they are necessarily the best way to structure an app you want to reason about. You end up with all sorts of mess. I support them because they are sometimes quite necessary to avoid other complexities and are the lesser of two evils. But I can count the components that use hooks in my 250k line code base on my hands. I totally agree on that, it's cargo culting in the codebase, we only had forms that were part of the routing around the time hooks came out, and they opened up the possibility to put forms anywhere without hving to deal with the setup (like starting the state machine), so from there they got overused and I don't think we have a single instance of a RAD form being started "manually" (it's either used directly in a router or with the hook)

tony.kay 2025-09-25T09:24:48.416079Z

I don’t know that there’s anything to document. This has nothing really to do with Fulcro, unless it turns out that the queryless component is preventing rendering in a way that’s a bug. That’s why I asked to see the sub-component, because I wanted to know if you’d accidentally prevented child renders there.

Eric Dvorsak 2025-09-25T10:34:25.135519Z

@tony.kay what is your approach for these kind of generic components, where the props aren't really something you are going to find in the app db, do you always pass them as computed props?

tony.kay 2025-09-25T12:20:03.063479Z

Computed props are part of rendering optimization for stateful components with a query.

Eric Dvorsak 2025-09-25T12:27:58.704069Z

I'm talking generic components like ui-drag-and-drop above, you often have props such as :supported-mime-types there that are known even at compile time since they are local config of the generic component.

Eric Dvorsak 2025-09-25T20:04:29.107569Z

@tony.kay so I had time to go back to that component to experiment here are the result: • I added a print at the top of ImportStudentModal and DragAndDrop and they both print everytime I type something, but when I drop a file:

[{(brian.model.organization/import-users-from-csv
   {:organization-id 1653,
    :timeframe-data {},
    :com.fulcrologic.fulcro.networking.file-upload/uploads
    [{:file/name "students.csv", :file/content {}}]})
  [:tempids *]}]
as you can see timeframe-data is empty meaning the function never updated • same result after changing ui-drag-and-drop to use computed props instead of props • same result after adding a query to ui-drag-and-drop • same result after adding use-hooks? true

Eric Dvorsak 2025-09-25T20:05:13.807689Z

I also rewrote ImportStudentModal with no hooks: and it still called onFileSelected with stale data

Eric Dvorsak 2025-09-25T20:44:35.842979Z

I think I finally figured out whats wrong

Eric Dvorsak 2025-09-25T20:45:34.464259Z

I added a button in the drag and drop. you can see 2 mutations one with stale data and one without

Eric Dvorsak 2025-09-25T20:45:56.407029Z

the one without is by clicking the button and after that dropping a file calls the muitation again with stale data

Eric Dvorsak 2025-09-25T20:53:58.352839Z

And here is a minimal repro:

(defsc PrintingButton [_ {:keys [onClick]}]
  {}
  (dom/div (dom/input
            {:id "file-input"
             :type "file"
             :accept "csv"
             :multiple false
             :onChange (fn [evt]
                         (when onClick
                           #?(:cljs
                              (onClick)
                              :clj evt)))})
           (dom/button {:onClick (fn [_]
                                   (println "before click")
                                   (onClick)
                                   (println "after click"))}
                       "Click to print")))

(def printing-button-ui (comp/factory PrintingButton))

(defsc TopChrome [this {:ui/keys [value]}]
  {:query [:ui/value]
   :ident (fn [] [:component/id :top-chrome])
   :initial-state (fn [_] {:ui/value ""})}
  (dom/div
   (dom/span "Enter something here:")
   (dom/div
    (dom/input {:onChange (fn [e] (m/set-string! this :ui/value :event e))}))
   (dom/div
    (printing-button-ui {:onClick (fn [] (println (str "" value ""))) :data value})
    (dom/span "You entered: " value))))

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

(defsc Root [this {:root/keys [top-chrome]}]
  {:query [{:root/top-chrome (comp/get-query TopChrome)}]
   :initial-state (fn [_] {:root/top-chrome (comp/get-initial-state TopChrome {})})}
  (top-chrome-ui top-chrome))

Eric Dvorsak 2025-09-25T20:57:40.899499Z

@tony.kay I think there is definitely a bug here I think it is in dom/input that closures get stalled

tony.kay 2025-09-26T05:37:52.268369Z

ah, are you using the latest RCs?

tony.kay 2025-09-26T05:38:28.247849Z

it is definitely possible that the new input wrapper isn’t updating properly

tony.kay 2025-09-26T05:39:32.653409Z

I didn’t think it had to do with hooks, and I couldn’t see how the other theory was true either, so I’m glad you came up with something that seems more plausible 😄

Eric Dvorsak 2025-09-26T06:36:19.485289Z

I'm on 813679f235252fe801f7a87a4b0a3b49f4a1336a

Eric Dvorsak 2025-09-26T06:36:31.924029Z

3.9.0-rc11-SNAPSHOT