Fork me on GitHub
Jakub Holý (HolyJak)10:12:57

Why does js/setTimeout inside dr/route-deferred break the order of transactions? This works, I get :route! to the target in question first, then dr/target-ready on it:

:ident  (fn [] [:component/id ::Accounts])
:will-enter (fn [app _]
              (dr/route-deferred [:component/id ::Accounts]
                                 #(comp/transact! app [(dr/target-ready {:target [:component/id ::Accounts]})])))
but if I instead execute the transact! inside a timeout (the motivation was to simulate a load that takes a while) it breaks and the target-ready appears in the transaction list before the corresponding :route! :
:will-enter (fn [app _]
              (dr/route-deferred [:component/id ::Accounts]
                                 (fn simulate-load []
                                   #?(:cljs (js/setTimeout
                                              #(comp/transact! app [(dr/target-ready {:target [:component/id ::Accounts]})])
How is this possible? When I look at the code, the :route! tx is submitted here right before the (completing-action) is called some 4 lines down. Even if the target-ready transaction happens async due to the setTimeout, it is only triggered after the route tx has been submitted. What is it I am missing?


@holyjak try 3.4.3 or so...I recently added a fix for route flickering that might be the problem

👍 3
Jakub Holý (HolyJak)14:12:11

3.4.3 behaves in the same way. I looked at the commits a way back but couldnt find the original fix for flickering. Trying 3.4.0 now. But the code in dr looks correct to me. Yet somehow this pseudocode, a simplifyication of the actual dr code

(comp/transact! :route! ["accounts"])
(js/setTimeout #(comp/transact! :target-ready ..) 10)
causes target-ready to occur before the :route! . Is it because transact! is asynchronous (in regard to attaching transactions to the tx list) so the first transact is not guaranteed to have modified the global tx list before the second transact runs 10ms later from a different "thread"? (yes, js has no threads...)

Jakub Holý (HolyJak)15:12:08

I have to digg more into it. 3.4.0 behaves the same. But if I execute the core of the dr function manually, the transactions appear in the correct order. I guess my simplification of the code lost something important:

    (require '[com.fulcrologic.fulcro.ui-state-machines :as uism])
    (uism/trigger! app
                   {:error-timeout 5000,
                    :deferred-timeout 20,
                    :path-segment ["accounts"],
                    :target [:component/id :com.example.ui/Accounts]})
    (binding [comp/*after-render* true]
        #(comp/transact! app [(ui-ready {:id :second :loc "within setTimeout"})])


Look at commit log


hello again, still on that thing about :account/id from yesterday, it was working yesterday, but today I spin again and it stopped, after some debug I figured the issue is in the fo/default-values part, because we use the account-id there, which is some data in the app state. it was working yesterday because I was in a "refresh flow", so it could pick up the data, but on fresh restart, it tries to compute default-values at app init time, but at this time the DB is not ready with the account-id, making it nil, and I noticed it only tries to compute that once. in normal Fulcro I could add this to the mutation logic so I have a delayed state to work with, what is the approach to fix this with RAD?


ok, I figured it out, the values on default-values can be fns, this fixes it:


fo/default-values {:computed-value (fn [] (compute-here))}

🎉 3

another thing, I like to have a virtual field that's a complex type, I see to create an attribute of any type if needs to use defattr, but I can't find how to define a complex type (a map in my case), how do I create an attribute with an open type? or map type? (consider its virtual, not needed for schema or saving anywhere)


I used :map and it just worked, I guess i can use any name here? but wonder if this is the appropriated way to do it

Jakub Holý (HolyJak)17:12:03

In what does > If you also are supporting form interactions then you can augment the form with form configuration data > mean? I believed that any time I want to use a component as a form - which I presumably want here since there are :form-fields declared on the components - I must add the form config to the data. Or?


I think that's just a substitute in ex code for a ...* mutation helper. In reality that would not be called as a separate component or as a ui renderer. hence the explicit call to merge/merge-component! Edit, I'm dumb, you say that (more or less) in your post already

❤️ 3
Jakub Holý (HolyJak)19:12:20

What I meant was, shouldn't the text in the Book be: > For users to be able to actually edit the form, you must augment it with form configuration data ? I can send a PR but want to check first.


users editing, and the existence of the form are 100% your code. Form state takes a picture of your entity when you tell it to, and can compute diffs, and can run a validator if you want. It is not some mutable-state-controls-your-form-with-magic system. It’s a set of functions for snapshotting state, comparing it over time, and checking if you consider that state “valid”. Remember: Fulcro is very literal about View = F(State)…form-state is about helping you go from state to state and compute interesting things about state.


TL;DR; I’m open to clarifications of book, but your suggested edit isn’t accurate 🙂


Perhaps “For the form-state helper functions to work…”

❤️ 3

Would probably be useful to write it like above. TBH I got lost reading the ex in the book, and I've already used FS pretty extensively.


isn’t that pretty much what the chapter intro says?


I pushed a minor edit, hopefully that is better


Cool. Yeah, that section by itself is confusing since realistically how often do you look at form-state with the intent to use it without form-state support? Default position would be "add form state config," but "you can skip if you want, it just won't do anything 🙂"


I am trying to fight against the pre-conceived notions that ppl have coming from systems that do form management,..that is sort of what RAD does, but form-state is not that.


when I wrote that chapter I was hoping the community would pop up their own form libs as add-ons, perhaps leveraging form-state as a building block


Oh. I’m not a target audience then. Fulcro is the first I’ve touched


didn’t happen, and now RAD exists, and the book is out of date

Jakub Holý (HolyJak)10:12:21

Thanks a lot for the edit, I think the book is clearer now.


next RAD question: we are trying to validate a consistency on the subform, in this case, its a check to see if the sum of percentages in the subforms is exact 100%, otherwise the form is not valid. We were able to prevent the form to submit by adding a form level validation, but now trying to figure how to display some error message for it


one thing I noticed is that, for the subforms field, the validator never tries to validate this field in specific, and given Fulcro RAD form errors must be attached to a field to give an error message (please correct me if this is a wrong assumption), how can we make a message for this to be visible?


the account forms have a sample global validation on email address


the invoice form has sample derived value calc


error does have to be attached to some field..there is no provision at the moment for the render plugin to show an error elsewhere….however, the form is just a defsc. If you provide the body you can render. You can also make your own derived plugin, or send PR on current one.

❤️ 3

I think form/render-layout is what the default body is…so if you wanted to wrap that, you can


The plugins are never meant to cover every case, and at the moment, there are some cases that should be covered but are not. There’s an escape hatch for every one that I’m aware of, which is part of the design.


My opinion on global errors is generally: if the form isn’t valid, you should be able to point to some field. Passwords don’t match is the classic case…which one is wrong? The first or second? Who knows…show the error on first or second or both. Your call.


what I just found was the option :validate-edges? false by default on the validator


I tried to trigger that on, but still not getting the ref edge validator called, doing some debug around it to understand why


ok, I think we reached something here with the debug


the issue is there the subform field is never considered completed by Fulcro


this means it never runs the subform field validation


by commenting the check for completed the thing worked as expected


but not sure what's the right way to go about this for a real fix


ok, I figure this works when we click to save, so, good enough, moving forward 👍


ok, Fulcro was right all along, it makes the field as complete when the subform fields are all filled, working great! 🎉

bananadance 3

The text is fine as-is. A form is nothing more than inputs you control. So, you can make one without form-state all day long. Form state is a ns of helpers that help you do what I consider the common bookkeeping tasks. If you don’t want to use form-state.cljc, then don’t. If you want those functions to work on a form, then you need to add the initial bookkeeping data they use.

👍 3

if you want to expand upon that in a PR I’d consider it, but really: you write the Form. You write the logic to put data in the entity. All form-state does is field state tracking, validations, and minimal diff calculation. If you want to write that stuff yourself, then go for it 🙂

😱 3

@holyjak have you had luck replicating your issue in RAD Demo?


FWIW I can see this when running your demo

Jakub Holý (HolyJak)10:12:34

Not yet but I now suspect it could be because of pre-merge I do in the prod app. Regardiing ☝️ do I understand correctly that when you switch Show inactive, the hard-reload will not show the account list as expected?


It doesn’t matter the status of the switch actually

Jakub Holý (HolyJak)21:12:32

Ok. So the GIF shows that hard reload is not working for you, correct or not?