fulcro

Book: http://book.fulcrologic.com, Community Resources: https://fulcro-community.github.io/, RAD book at http://book.fulcrologic.com/RAD.html
tvaughan 2021-01-05T16:16:41.071600Z

I'm trying to update the property of a component in a state-machine event handler. I'm calling transact!! and I see that the property value has been set in the db when looking at it in the inspector. However, a printf in the component shows that the property is still nil. I'm assuming I shouldn't directly update the state-map from the env passed to the handler, right? Should I use something other than transact!!?

tvaughan 2021-01-06T12:02:40.096300Z

> so you had some stale weird compile issue? No, I doubt this is the case. I do consistently have issues with hot-code reloading. I frequently see differences between automatic hot-code reload, shift+reload, and restarting the watcher. Because I changed a compiler option I simply wanted to make sure I didn't introduce yet another variable in the equation with the build possibly being inconsistent. I'm pretty sure I made changes to the form fields yesterday during my tests, and didn't see the component re-render. I tried a lot of different things. I simply can't say I remember doing this with complete certainty. That said, this was working. I even shared a video with our team of how this form behaved; errors were displayed immediately, and disappeared when any field was edited. I've been making changes to some seemingly unrelated components. I can't think of anything that would have caused this component to break.

tvaughan 2021-01-06T12:12:59.096500Z

> I definitely would not use it unless I measured a performance problem with a production build. Thanks for the clarification. I remember when this feature was introduced. We had problems with it at the time. However, I thought I remember seeing a post here in #fulcro sometime later that said to me it was stable-ish, and also the recommended default. So we turned it back on, and I haven't noticed any problems with it since. Our production traffic is light enough that we can afford to experiment a bit, and help with testing. Given the opportunity, I think it's better to adopt new features sooner rather than later, especially if they're intended to become the recommended, default approach, rather than wait until the code base grows to such a size that switching over becomes prohibitive. I misunderstood the intent of this feature.

tvaughan 2021-01-06T12:19:03.096700Z

> Otherwise, component re-render is based completely on props and state, the former of which is completely based on query. Good. This has been my understanding too. However, I don't see this behavior. The only thing that appears to be different is that this component includes form state handling (and is a state machine actor). Would I be wasting my time if I looked more closely at the form state handling?

tvaughan 2021-01-06T12:21:56.096900Z

> !! variants only re-render the component from which the transact came I'm using the global app in the call to transact!!. In the state machine handler I don't have access to the component, i.e. this. Is this what you're referring to?

tony.kay 2021-01-06T14:57:07.113200Z

• The !! variants and unwrapped inputs sounded like a good idea at the time, but they didn’t improve performance in any measurable way I’ve been able to detect (large forms could definitely see an improvement from !!, but probably not from the wrapped vs unwrapped) • form-state is pretty old and stable. If you’re still having a problem I would put money on the data you’ve got in your state. Is it normalized correctly? Does it all join back to root? Do your queries from root join together and follow the data graph, etc? @tvaughan I missed the fact that you are using UISM. Sorry. A state machine event handler can use apply-action to update the state map. A UISM handler MUST not do transact! or transact!!. Sorry, I missed that you were in a state machine. If you submit a synchronous transaction from a UISM, then your transaction will end up having no effect, since it runs on the thread, updates state, but UISM is meant to be side-effect free (and is in the middle of managing state for you, so it will overwrite the sync changes)

tony.kay 2021-01-06T14:58:01.113500Z

Your tools in UISM are apply-action and trigger-remote-mutation

tony.kay 2021-01-06T14:58:17.113700Z

Think of UISM as a “mega-mutation”

tvaughan 2021-01-06T15:20:08.113900Z

Thanks a lot @tony.kay. I'll replace the !! variants with their ! counterparts. The use of transact!! in the state machine handler was my attempt at a workaround where I duplicated properties across two components. I've since removed this and have gone back to a link query. Everything seems to be correct per what I see in the browser and inspector, except the component with the link query doesn't update until one of its properties changes even though the component in the link query has changed. For example, Component Foo has two form fields, a link query to Component Bar, and displays some of the properties in Component Bar. When a button is clicked in Component Foo, its two form fields are passed to uism/trigger! which eventually populates Component Bar. Everything up until this point appears to be correct. However, the updated property values in Component Bar are not displayed in Component Foo until one of the two form fields in Component Foo are changed, e.g. a character is added or deleted. I went back to the documentation, and Component Bar is in the initial-state of Component Foo. I'm sorry that I didn't explain this clearly from the start.

tony.kay 2021-01-06T20:11:56.126200Z

If you want to generate a small repro case I can take a look. Link queries should update just fine. Most everything in your problem description should be immaterial. Render (the default one) sends props from root. I know db->tree constructs props correctly for link queries. That is heavily tested. Components will only short-circuit rendering if their props don’t change, and putting a link query in there will cause them to change assuming it is a join that includes the subprops that are actually changing, and not just the ident. There’s really almost nothing to it. Look at the source code of the renderer: https://github.com/fulcrologic/fulcro/blob/develop/src/main/com/fulcrologic/fulcro/rendering/keyframe_render.cljc#L28

tony.kay 2021-01-06T20:13:37.126500Z

it is literally 3 lines of code (lines 25, 29, and 33): look up how to call VDOM library (React in this case), Convert db to a tree of props using your query, pass that tree of props to your root.

tony.kay 2021-01-06T20:13:58.126700Z

Fulcro is not doing any magic, and it really helps to understand just how simple the picture is.

tony.kay 2021-01-06T20:15:51.126900Z

you could run line 27/29 in your REPL and look at the props, which will be identical to a pprint of props in your Root. Is the data changing in that, because the only other thing that Fulcro does is auto-create a shouldComponentUpdate that basically says (basically) (not= prior-props-and-state current-props-and-state)

tony.kay 2021-01-06T20:16:53.127100Z

Optimized render can do some additional help based on idents: It can run the query starting at a particular component to render a subtree…but the default renderer does not bother doing that unless you ask it to try (via, for example, a synchronous render or :only-refresh option).

tony.kay 2021-01-06T20:19:31.127400Z

So, given your description: 1. Using link query. If you’re doing that correctly (db->tree gives back the props you expect to see), then that isn’t the problem 2. You actually have data the graph connected (can be manually checked by visually walking db in Inspect or using db->tree) 3. You understand which rendering optimizations you are triggering, but in this case you have a subtree to update, so that should not matter.

tony.kay 2021-01-06T20:20:19.127600Z

so, my advice is to try running get-query on your root, use db->tree to get the props, (or just log/analyze what you’re getting in the render bodies, starting from Root)

Jakub Holý 2021-01-06T23:11:59.151800Z

> the default renderer does not bother doing that But isn't the default multiple-roots-renderer , which derives (I believe) from ident-optimized-render ?

tony.kay 2021-01-06T23:13:11.152500Z

no and no 😄

tony.kay 2021-01-06T23:13:30.152700Z

default in RAD is MRR, but in Fulcro general it is k2

tony.kay 2021-01-06T23:13:39.152900Z

nothing derives from IOR

tony.kay 2021-01-06T23:18:18.154Z

😛 Shows you what I manage to remember

tony.kay 2021-01-06T23:18:27.154200Z

MRR is KR2 w/floating roots

Jakub Holý 2021-01-06T23:19:04.154400Z

Thank you! I somehow believed that MRR is ident-optimized w/ floating roots. Great to be corrected ❤️

tony.kay 2021-01-06T23:19:04.154600Z

ident-optimized was the original Fulcro approach that was based on an Om Next optimization. When I measured the performance though, it wasn’t a win for most apps.

👍 1
tvaughan 2021-01-07T17:27:22.214400Z

Thanks a lot for this very detailed write-up, @tony.kay. I really appreciate this. However, I'm not any closer to understanding what's going wrong. When I look at the current db in the inspector, or using get-query and db->tree as above, everything looks correct. Below is a very abbreviated version of what I'm working with. This has gone through many different iterations. I understand there are different ways to structure this. Depending on the approach, either 1) when :session/error changes in the Session component the SignIn component doesn't re-render, or 2) when the submit button is clicked both :email/addr and :user/password are nil (even though these have the correct values as shown in the inspector). The latter is what happens with the example below. Given everything I've seen so far in the documentation and examples this seems to be the most common approach. I see in the inspector that :email/addr and :user/password have the expected values, however props in the SignIn component only contains :sessions/session when the submit button is clicked. I also see an error message about controlled vs uncontrolled inputs. Using :value on the two input fields does eliminate this error message, but when these input fields are edited they flicker between the edited value and the value set in :value. FYI. Does anything jump out as missing or incorrect? I

tvaughan 2021-01-07T17:27:22.214600Z

(defsc Session
  [_ {:keys [session/token
             session/current-user
             session/error]}]
  {:query [:session/token
           :session/current-user
           :session/error]
   :ident (fn [] [:component/id :session])
   :initial-state {:session/token nil}}
  (when (and token (not error))
    (dom/p
     (:current-user/email-addr current-user))))

(def ui-session (factory Session))

(defn- field-ok?
  [props field]
  (and
   (-> props field count pos?)
   (form-state/checked? props field)))

(defn- submit-ok?
  [props]
  (and
   (field-ok? props :email/addr)
   (field-ok? props :user/password)))

(defsc SignIn
  [this {:keys [sessions/session
                email/addr
                user/password]
         :as props}]
  {:query [{[:sessions/session '_] (get-query Session)}
           :email/addr
           :user/password
           form-state/form-config-join]
   :ident (fn [] [:component/id :signin])
   :initial-state (fn [_]
                    {:sessions/session (get-initial-state Session)
                     :email/addr ""
                     :user/password ""})
   :route-segment ["signin"]
   :form-fields #{:email/addr :user/password}
   :pre-merge (fn [{:keys [data-tree]}]
                (form-state/add-form-config SignIn data-tree))}
  (let [submit-ok (submit-ok? props)
        error (:session/error session)]
    (dom/div
     (when error
       (dom/p error))
     (dom/div
      (dom/input {
                  :id "email-addr"
                  :name "email"
                  :type "email"
                  :autoComplete "username"
                  :required true
                  :defaultValue (or addr "")
                  :onChange (fn [evt]
                              (let [value (.. evt -target -value)]
                                (set-value! this :email/addr value)
                                (transact! this [(form-state/mark-complete!
                                                  {:field :email/addr})
                                                 (ui-mutations/set-props
                                                  {:ident [:component/id :session]
                                                   :props {:session/error nil}})])))})
      (dom/input {
                  :id "password"
                  :name "password"
                  :type "password"
                  :autoComplete "current-password"
                  :required true
                  :defaultValue (or password "")
                  :onChange (fn [evt]
                              (let [value (.. evt -target -value)]
                                (set-value! this :user/password value)
                                (transact! this [(form-state/mark-complete!
                                                  {:field :user/password})
                                                 (ui-mutations/set-props
                                                  {:ident [:component/id :session]
                                                   :props {:session/error nil}})])))})
      (dom/button {
                   :type "submit"
                   ;; :disabled (not submit-ok)
                   :onClick #(uism/trigger! this :sessions/state-machine :event/create-session!
                                            {:email/addr addr :user/password password})}
                  "Sign in")
      ))))

(def ui-signin (factory SignIn))

tvaughan 2021-01-07T17:27:42.214800Z

I've tried this with 3.4.10 and 3.4.12

tony.kay 2021-01-07T18:12:25.216200Z

you read this, right? https://book.fulcrologic.com/#_a_warning_about_ident_and_link_queries

tony.kay 2021-01-07T18:12:59.216400Z

I see you have initial state, but I am concerned there is something going on where you’re perhaps clearing the state of SignIn

tony.kay 2021-01-07T18:16:08.216700Z

There’s no way for me to diagnose this without a minimal repro case that actually shows all moving parts (server side, state machine, etc). The only thing I would probably not do how you’re doing it is the initial state. The {:sessions/session (get-initial-state Session) part is questionable. That data comes from root, and should really be initialized at root. I’ve never tried to do it the way you’ve done it, and don’t know that it will initialize the root stuff properly. That bit goes in the initial state of your Root component, or gets merged into root on startup. Having a random child initialize something at root as if it were part of its own state is a real potential problem…you could end up with a local ident for that field, which could (but should not) confuse the query engine.

tony.kay 2021-01-07T18:16:37.216900Z

at the very least it is mixing two paradigms: link queries jump back to root. Initial state is about component local concerns.

tvaughan 2021-01-07T18:23:01.219100Z

> you read this, right? Yes. As best as I can tell I've set this up properly.

tvaughan 2021-01-07T18:23:28.219300Z

> and should really be initialized at root. It is.

tvaughan 2021-01-07T18:24:05.219500Z

> I’ve never tried to do it the way you’ve done it I've duplicated it in the SignIn component for no particular reason. I wasn't aware this couldn't be duplicated safely

tvaughan 2021-01-07T18:26:01.219700Z

> where you’re perhaps clearing the state of SignIn This is certainly a possibility. However, this looks correct when viewed in the inspector

tvaughan 2021-01-07T18:28:44.220300Z

I'll see if I can create a stand-alone, reproducible example.

tony.kay 2021-01-07T18:31:17.220500Z

also: don’t use defaultValue. Use :value. Control the input. :value (or email "") is how you get around nil warnings in console (or properly initialize things)

tvaughan 2021-01-07T18:36:17.220700Z

I understand that's the correct approach, but I've noticed that when I use :value the form loads, something is typed in or loaded via a password manager, and then this is erased and replaced with the original value, e.g. an empty string. This happens once. Afterwards things behave as expected

tony.kay 2021-01-07T18:36:30.220900Z

And you say that the tree of props you get from (db->tree (get-query Root) state-map state-map) exactly matches your UI tree

tvaughan 2021-01-07T18:37:45.221100Z

It matches what I see in the inspector which is correct

tony.kay 2021-01-07T18:40:43.221300Z

There is nothing unusual about your code, so I strongly suspect you have a data error. The ui is a pure mirror of the data. The rendering implementation is dead simple. If you get the data right, the UI will follow. So, when you tell me “when :session/error changes in the Session component the SignIn component doesn’t re-render” that means either: 1) you’re not using the default renderer, and are possibly getting a targeted refresh of just the session (did IT change? i.e. you log in render of both, but yo uonly saw a render of the child?). or 2) The data in props didn’t actually change (which means the data isn’t hooked up, and the ui data tree didn’t gen right as a result).

tvaughan 2021-01-07T18:43:11.221600Z

> when you tell me “when :session/error changes in the Session component the SignIn component doesn’t re-render” Sorry @tony.kay. I haven't explained this clearly. I've tried many different approaches. These approaches seem to result in one of two outcomes, either 1) the SignIn component doesn't re-render when session/error changes, OR 2) the values for addr and password are nil when the submit button is clicked. In the example above, only the second case happens. The rending of session/error works as expected

tvaughan 2021-01-07T18:43:20.221800Z

Again, sorry for not having explained this clearly

tvaughan 2021-01-07T18:51:11.222Z

The SignIn component not re-rendering happens when I use transact!! and/or set-value!!, and perhaps in other cases I'm not thinking of right now

tvaughan 2021-01-07T18:56:42.222200Z

At the time the submit button is clicked, the SignIn props only contains a key for :sessions/session when printed to the console, nothing else. However, these are shown in the inspector before and after the button is clicked

tvaughan 2021-01-07T18:59:26.222500Z

Attempting to also display them in the html output shows that they're always nil

tony.kay 2021-01-07T19:02:59.222700Z

“Shown in Inspector” is the thing I’m not sure I believe you: Are you saying they are right in their localized table, or that all of the joins from root are properly represented. Transacting and setting them will always work, but they won;t come back if your query is broken or you did not join the graph in the db.

tony.kay 2021-01-07T19:03:49.223Z

and yes, the intentional behavior of !! is not to render anything but the component used in the transact (or none if you use app)

tony.kay 2021-01-07T19:04:10.223200Z

if addr/password are nil, you don’t have aconnected data graph

tony.kay 2021-01-07T19:05:45.223500Z

what do the parent queries look like? What does the parent initial state look like?

tony.kay 2021-01-07T19:05:51.223700Z

it all has to join to root

tvaughan 2021-01-07T19:07:19.224Z

I'm not sure I understand. The SignIn component isn't mounted in the root component, session is though. SignIn is routable and is in the root router. Otherwise it's just a "normal" component

tvaughan 2021-01-07T19:10:29.224400Z

The signin component is rendered using dynamic routing in a state machine, or a redirect when a session token does not exist (as is the case here).

tvaughan 2021-01-07T19:12:36.224600Z

> the intentional behavior of `!!` is not to render anything but the component used in the transact Even when I scheduled a render explicitly, or used app, I still couldn't get the render to happen

tony.kay 2021-01-08T17:47:29.252800Z

If you want to post a minimal repro case I can look. This hand waving is too much a drain on my time, sorry

tvaughan 2021-01-08T17:59:49.253Z

I understand completely. I'll see what I can do. Thanks for your help @tony.kay

tvaughan 2021-01-11T20:48:11.294100Z

diff --git a/example/src/example/ui/components.cljc b/example/src/example/ui/components.cljc
index 74f76d0..45fdd5f 100644
--- a/example/src/example/ui/components.cljc
+++ b/example/src/example/ui/components.cljc
@@ -197,14 +197,17 @@
 
 (defsc Root
   [_ {:keys [sessions/session
+             root/signin
              root/site-chrome]}]
   {:query [{:sessions/session (get-query Session)}
+           {:root/signin (get-query SignIn)}
            {:root/site-chrome (get-query SiteChrome)}]
    :initial-state (fn [_]
                     {:sessions/session (get-initial-state Session)
+                     :root/signin (get-initial-state SignIn)
                      :root/site-chrome (get-initial-state SiteChrome)})}
   (if (-> session :session/current-user :current-user/email-addr)
     (ui-site-chrome site-chrome)
-    (ui-signin {:sessions/session session})))
+    (ui-signin signin)))
 
 (def ui-root (factory Root))
@tony.kay This appears to have done the trick. To be clear, the SignIn component is not used in a link query, the Session component is. The SignIn component is also routable, so it can either be rendered here or via the site router. We have other components that have forms and are not included in the Root component (they're routable too), and they don't have the same problem (property values are nil when the form is submitted). I guess I was breaking some fundamental principle where every factory used in a component must have an appropriate entry in that component's query? Thanks again for your help and the time you spent on this

tony.kay 2021-01-11T21:35:11.294300Z

yep, simple as that: UI props tree matches query tree. No query there causes no denormalizaation

Jakub Holý 2021-01-05T16:39:48.071900Z

if the data changes in the DB but the component does not reflect it then I would guess Fulcro did not know it needed to refresh/rerender the component. Or?

tvaughan 2021-01-05T16:45:54.073800Z

Right. I remember the book mentions the need to add a refresh flag in some circumstances. I wonder if that applies here?

tvaughan 2021-01-05T16:48:49.075100Z

The property is a “ui property” e.g. :ui/error-msg, if relevant

tony.kay 2021-01-05T17:13:50.075300Z

Nil likely means a spelling or graph join data error. Otherwise it's a bug, but my money's on your data

tvaughan 2021-01-05T17:22:15.075600Z

I've definitely had spelling errors in the past. I'll check again. Thanks

Jakub Holý 2021-01-05T17:42:39.076400Z

The screencast demonstrating troubleshooting a Fulcro app I promised is now live https://youtu.be/1H1FZ0CEC60 - Demo of troubleshooting a Fulcro RAD app It is not perfect but hopefully useful enough.

👏 2
Jakub Holý 2021-01-05T17:45:37.076700Z

Let me know if you stumble upon something I should add to https://blog.jakubholy.net/2020/troubleshooting-fulcro/#_frontend_fulcro 🙂

👍 1
tony.kay 2021-01-05T18:10:25.077100Z

Also, try transact! to eliminate !! as a variable.

tony.kay 2021-01-05T18:11:50.077300Z

@holyjak some more tips perhaps for your troubleshooting. If having an issue: • Did you override the tx processing? • Are you using a non-default rendering optimization plugin?

tony.kay 2021-01-05T18:14:06.077500Z

something like synchronous tx processing, rendering plugins should be avoided by beginners. The !! variants also can cause confusion because they are an extreme form of optimization that should at least be tried in a single ! variant when observing a problem, since they are an optimization that can cause confusion about whole-app rendering.

tvaughan 2021-01-05T18:29:04.077700Z

@tony.kay Should I also delete :external-config {:fulcro {:wrap-inputs? false}} ?

Jakub Holý 2021-01-05T18:30:59.077900Z

Thank you for the tips! I will try to find the right place for these. What is a "rendering optimization plugin"? Would :optimized-render! com.fulcrologic.fulcro.rendering.keyframe-render2/render! fall into this category?

tony.kay 2021-01-05T18:43:56.078100Z

@tvaughan those options are much less tested and used. If you’re experiencing what looks like a bug, they would be the highest suspects. I still suspect your data, esp if you’re seeing println being called, since that indicates render happened.

tvaughan 2021-01-05T18:46:36.078300Z

I misunderstood the sequence of printfs. I don't see any printfs after the initial render

tvaughan 2021-01-05T18:47:35.078500Z

I added a random string to the property value. I see the property value does change and has a different random number each time. However, the component doesn't update, no printf

Jakub Holý 2021-01-05T18:56:46.078700Z

and if you force-rerender, does the new value show up?

tvaughan 2021-01-05T19:10:34.078900Z

I deleted the wrapped inputs setting, switched to transact! and added the refresh option, no change. How else would I force a re-render?

tvaughan 2021-01-05T19:16:10.079300Z

I see a react error message with wrapped inputs deleted. Putting that back now...

tvaughan 2021-01-05T19:23:16.079600Z

OK. Getting closer. The component also uses form config. I see the property being updated (not a form field), but it only renders in the browser after a form field is edited (which marks it complete). Still using transact! and refresh

tvaughan 2021-01-05T19:32:26.079800Z

I cleared my build cache and started over. I'm back to using wrapped inputs, and transact!! without a refresh option. The property value renders when I edit a form field ...

Jakub Holý 2021-01-05T20:11:04.080100Z

See the troubleshooting guide - (app/force-root-render! com.example.client/app)

👍 1
tvaughan 2021-01-05T20:16:12.080300Z

Thanks for the help Jakub and Tony

tony.kay 2021-01-05T20:45:12.080600Z

@tvaughan so you had some stale weird compile issue?

tony.kay 2021-01-05T20:48:14.080800Z

So, just for your general edification: • !! variants only re-render the component from which the transact came • Wrapped inputs flag is largely irrelevant. The wrapping is super cheap. I never use it myself. If you don’t use wrapped inputs, then cursor jumps can happen if you do non-sync transactions, so if you add this optimization you’re asking for a “bug” that is user visible, but that doesn’t really help anything to introduce….I definitely would not use it unless I measured a performance problem with a production build. • Otherwise, component re-render is based completely on props and state, the former of which is completely based on query.

❤️ 1
exit2 2021-01-05T22:15:15.081500Z

I’m trying to use df/load-action as follows:

exit2 2021-01-05T22:15:28.081800Z

(df/load-action env :fetch-booking-quote CreateBookingForm
	{:target        [:create-booking :tab :quote]
	 :refresh       [f/form-root-key]
	 :params        {:payload       payload
	                 :detail-values detail-values}
	 :post-mutation `post-fetch-quote})

exit2 2021-01-05T22:16:46.082200Z

But when the data comes back it starts populating the state as:

exit2 2021-01-05T22:16:50.082500Z

[nil nil :COMPONENT/by-id :create-booking-form :total]

exit2 2021-01-05T22:17:05.082900Z

and the more times I change my input field, it starts adding more nils to the beginning

exit2 2021-01-05T22:17:54.083700Z

If I don’t pass the component in the df/load-action and just pass nil, it solves this issue but causes all my other fields to flash the pristine value on keystroke

tony.kay 2021-01-05T23:58:32.084Z

@njj Fulcro 2?