Fork me on GitHub

Hey all, I'm working on a blog post that includes creating a fulcro form. I'd love to hear thoughts on whether this implementation is idiomatic from anyone who has time to take a look:

👍 4

yeah seems correct, some things i would a little differently but they are cosmetic


like i usually call mark-complete on blur


also a subform example might be useful


to show the power of normalization


otherwise it might be unclear how this is all that useful


that example might be too involved for what you want to do, but it does highlight several useful aspects


in your blogs you could also link to the relevant parts of the book as a reference if someone wants more detail


I have done that a bit, but could definitely do it more. Good suggestion, thanks.


no problem, thanks for writing these by the way!


I'm not sure how to fit a subform into this application at the moment; I'll take some time to think about that.


You're right that it would make for a better demonstration of the usefulness of form-state


yeah if form-state didn’t sub-form -> flat diff delta mapping, it would be waaay less useful


the flat diff delta is amazing, it’s so easy to write a generic function that can turn it into a DB transaction for any particular DB


RAD’s DB adapters do exactly that


and it’s only possible b/c fulcro stores app state in a global normalized way


I am trying to mark form as complete in :pre-merge , where I have (and return) a data tree for component. Unfortunately mark-complete function only works on normalized state map… is there some other way?


@roklenarcic You can just reach into the form config and set them…there should be a utility


it’s just a map


the complete marker is just a set of keywords


I guess there should be mark-complete* for inside mutatoins, mark-complete! as a top-level call, and mark-complete for data trees


yeah, I can try to copy the logic… currently I am hitting a wall guard-rails failing add-form-config's return type which is weird since it’s return spec is `

(s/keys :req [::config])
which it generates by itself


timbre_support.cljs:80 ERROR  com/fulcrologic/fulcro/algorithms/form_state.cljc:122 add-form-config's return type
 -- Spec failed --------------------

  {:app.model.sepa/full-name "",
   :app.model.sepa/address ...,
   :app.model.sepa/iban ...,
   :com.fulcrologic.fulcro.algorithms.form-state/config ...}

should satisfy

  (partial app.model.sepa/max-str 140)

or value

  {:app.model.sepa/full-name ...,
   :app.model.sepa/address [],
   :app.model.sepa/iban ...,
   :com.fulcrologic.fulcro.algorithms.form-state/config ...}

should satisfy

  (<= 1 (count %) 7)

-- Relevant specs -------

  (cljs.core/partial app.model.sepa/max-str 140)

-- Spec failed --------------------

  {:app.model.sepa/full-name ...,
   :app.model.sepa/address ...,
   :app.model.sepa/iban "",
   :com.fulcrologic.fulcro.algorithms.form-state/config ...}

should satisfy


-- Relevant specs -------

  (cljs.core/partial app.model.sepa/max-str 140)

Detected 2 errors


why does failing general specs on input data fail guardrails on return spec of add-form-config , makes no sense…

Jakub Holý (HolyJak)20:04:19

I do not know anything about this but the :com.fulcrologic.fulcro.algorithms.merge/not-found is a hint. Is something wrong with the state of the entity, i.e. the input data that feeds into add-form-config ? What and why? You require full-name but somehow it is missing / Fulcro is unable to generate a prisitne state for it. Can you see anything there?


yeah but the error says there’s something wrong with add-form-config’s return type

Jakub Holý (HolyJak)20:04:43

Yes, because it contains the .../not-found keyword where there should be a string. If you started with a valid entity - with valid data without "not found", it would have worked I assume.

Jakub Holý (HolyJak)20:04:40

When you fetch data from Pathom and ask for an attribute that it cannot resolve, it sets the value to this not-found key.


OK I now return "" and that does not match the spec for that form element (at least 1 character is required)


and I still get an error… is the implication here that I cannot add form config to data that doesn’t validate on form field spec?


would be weird if that was true

Jakub Holý (HolyJak)20:04:32

Sorry, I have no idea about that. I was just guessing to try help you troubleshoot the problem.


@roklenarcic i’ve hit the same issue with guardrails validating my specs on add-form-config for whatever reason jfyi you can disable guardrails to throw exceptions on errors (so that it would just print them)


I’ll open an issue, it seems to be validating everything it can find…


@roklenarcic this is not specific to guardrails


It is a “feature” of spec that I’m still annoyed with and struggling to cope with when writing specs.


If you say map?, then spec seems to check every key in the map…then again, I copied a lot of guardrails from ghostwheel, so perhaps that library does that? I don’t think that’s the case…I think it is spec, and there is nothing that can be done on our end other than, as @UDQ2UEPMY suggested, turn off throwing (which I turn off for dev, and on for tests).


unfortunately, this means you’ll get warnings from guardrails (or even exceptions) when you put a thing in a map and doesn’t fit the spec, even for a transient time period. On the one hand I see why, but on the other it prevents you from doing specs for intermediate helper functions that might use a key when in some intermediate state, or in the case of FUlcro the form-state config had to have its spec changes to be “this map of stuff, or an ident” so that it would die when seeing that key normalized.


I’ll close the issue


I always forget about this totally stupid undocumented crap in spec


@tony.kay thanks for clarification, i thought that was the exclusive feature of s/keys to check all the keys, didn’t know about map?

Jakub Holý (HolyJak)19:04:08

According to this, it does not do it for map?, at least not in this simple case

(s/def ::int integer?)
=> :com.example.client/int
(s/valid? map? {::int "I fail"})
=> true
(s/valid? (s/keys) {::int "I fail"})
=> false


This is Guardrails checking code that runs when enabled:

(defn run-check [args? {:keys [log-level vararg? throw? fn-name]} spec value]
  (let [vargs?          (and args? vararg?)
        varg            (if vargs? (last (seq value)) nil)
        specable-args   (if vargs?
                          (if (map? varg) (into (vec (butlast value)) (flatten (seq varg))) (into (vec (butlast value)) (seq varg)))
        valid-exception (atom nil)]
      (when-not (s/valid? spec specable-args)
        (let [config  (assoc log/*config* :output-fn output-fn)
              problem (exp/expound-str spec specable-args)]
          (log/log* config (or log-level :error) (str fn-name (if args? " argument list" " return type") "\n") problem)
          (when throw?
            (reset! valid-exception (ex-info problem {:fn-name (str fn-name)})))))
      (catch #?(:cljs :default :clj Throwable) e
        (log/error e "BUG: Internal error in expound or clojure spec.")))
    (when @valid-exception
      (throw @valid-exception)))


So I have no idea why valid? for map? in GR would be any diff from what you just did, yet I’ve seen it fail like @UDQ2UEPMY is saying, so not sure what is up. My assumption from my obsv. was that it was checking the keys…not sure what is going on now.

Jakub Holý (HolyJak)20:04:33

Works for me:

(quard/run-check false {:throw? true} map? {::int "I fail"})
=> nil
(quard/run-check false {:throw? true} (s/keys) {::int "I fail"})
Error: -- Spec failed --------------------
so something else is at play here...

Jakub Holý (HolyJak)20:04:52

I have tried to replicate the form problem but failed. Any tips? I expected this to fail with a Spec error but it did not:

(comp/defsc NameComp [_ {::keys [name]}]
    {:query [::name]}
    (dom/p name))
(s/def ::name string?)
(fs/add-form-config NameComp {::name :not-found})
Tony: No problem with conform either:
(s/conform map? {::int "I fail"})
=> {:com.example.client/int "I fail"}


no ideas myself


version of spec???

Jakub Holý (HolyJak)06:04:18

I use the one that comes with RAD, 0.2.176 @roklenarcic Could you try my code snippet ☝️ and see whether it fails for you?

Jakub Holý (HolyJak)20:04:42

@tony.kay I am online now for a while, if you want any assistance with


@holyjak sorry, I just have bigger fish to fry. If you find something and want to send a PR fine, but I don’t want to discuss or even see issues like this for the time being. If I run into them myself I’ll fix them. If you want to patch something you see a fix for, great. otherwise, I’m sorry, I just don’t have the bandwidth for the distractions

👍 4
Jakub Holý (HolyJak)08:04:16

Ok, got it. I will stick to the version of RAD that works then. I will try to dig into the code base but it will take me quite a while to be able to understand it somewhat :'( I'd love to send a PR but I am very far from having the knowledge to be able to make one.