Fork me on GitHub

I have a few questions regarding the Fulcro book. First, in Ch. 2.5.1, should (merge/merge-component! app Person {:person/id 11 *...*} :append [:list/id :friends :list/people]) instead be adding this to the graph {:list/id {:friends {:list/people [*...* [:person/id 11]] ~:list/id :friends *...*~}} :person/id {11 {:person/id 11 *...*}}}, i.e. without the strikethrough portion?


Secondly, in Ch. 3.5.1 and Ch. 4.1, both contain an important section about using reader conditional to pull in proper server DOM functions. However, I cannot find com.fulcrologic.fulcro.dom-server namespace. Also, Ch. 4.1 mentions fulcro.client.dom which does not exist. Is the reader conditional still required?


Lastly, a small typo. In Ch. 4.1 {:style {:colour :red}} should be {:style {:colour "red"}} instead. Thanks.

Jakub Holý (HolyJak)11:12:26

The striked through portion is supposed/required to be there from before. I guess it could be removed from the example if it is confusing. should be com.fulcrologic.fulcro.dom I believe. Do you want to send a PR to fix it?. The dom-server ns is right here Are you sure the color prop is a typo? Eg for classes both string and keyword values are supported.


Thanks @U0522TWDA for your reply. 1) The strike is added by me and not in the book, so it would be great to know which version is correct and just keep the correct one. 2) Found the dom-server ns, overlooked it, thank you. Since you are already familiar with the PR, would you mind helping with the PR for the typos when you have the time? Much appreciate it. 3) I think the colour prop is a typo because I cannot get it to work in the keyword form. It has to be in string format to my understanding. 4) I found another typo which can be grouped into 1 typo PR. Details to come below.


In Ch. 3.9.1, as shown in the screenshot below and indicated by the green arrow:

Jakub Holý (HolyJak)12:12:45

To make a PR just go to and click on the edit icon, make the changes, click the buttons. In 1) neither is wrong. The :list/id is not added by the merge but is shown, together with the... to hint about the pre-existing data.

Jakub Holý (HolyJak)12:12:53

It seems you are right about the delete-person. A little later it is defined as (defmutation delete-person "Mutation: Delete the person with :person/id from the list with :list/id" [{list-id :list/id person-id :person/id}]..


Thanks, @U0522TWDA. To complete my message above regarding the other typo, the green-underlined part should be {:list/id id :person/id person-id}, or following the flow of the book, it should still be {:list-name label :name name} before the later changes are introduced.

Jakub Holý (HolyJak)13:12:32

I believe the former

Jakub Holý (HolyJak)11:12:31

@tony.kay this sentence looks wrong > Fulcro is designed to be fast enough that then your application should be fast enough to do database updates on every keystroke, > but I'm not sure what is the right correction. Perhaps drop the 2nd line? > Fulcro is designed to be fast enough that then your application should be fast enough to do database updates on every keystroke,


In, it says:

Normally you might throw this into your application state with something like:

(merge/merge-component! app PersonForm person-tree)

which will insert and normalize the person and phone number.

If you also are supporting form interactions then you can augment the form with form configuration data like so:

(def person-tree-with-form-support (fs/add-form-config PersonForm person-tree))
(merge/merge-component! app PersonForm person-tree-with-form-support)
It's not clear to me where I would put the call to merge-component! . I have a simple form with a single input :note/text, and I would like to use the form to save new notes. NoteForm looks like this:
(defsc NoteForm [this {:note/keys [id text] :as props}]
  {:query [:note/id :note/text fs/form-config-join]
   :initial-state {:note/id (tempid/tempid) :note/text ""}
   :ident :note/id
   :form-fields #{:note/text}}

Björn Ebbinghaus16:12:17

You call merge-component! (or its non ! version) inside of a mutation. Every function that works on the app state usually belongs in a mutation.

Björn Ebbinghaus16:12:35

(defmutation add-note [{:note/keys [id text]}]
  (action [{:keys [app]}]
    (mrg/merge-component! app Note #:note{:id id :text text} 
      :append [:all-notes]))) ; this is optional, but I want to show you how to use targeting.


Okay, so I'm calling the mutation like this:

(comp/transact! this [(save-note #p props)])
and then my mutation looks like this:
(defmutation save-note [{:note/keys [id text] :as props}]
  (action [{:keys [state]}]
    (swap! state
      (fn [s] (log/info "Saving Note with" id)
                (fs/add-form-config* s (comp/registry-key->class :buidler.ui/NoteForm) [:note/id id])
                (save-note* id text)
                (targeting/integrate-ident* [:note/id id]
                                            :append [:note-list


It still tells me that my form state is not normalized, and I can't figure out how to reset the NoteForm to have a new tempid (so I can start adding a new note by inputting text) after a successful save.

Björn Ebbinghaus16:12:13

So.. I wouldn’t use tempids for ui Stuff. It is used as a placeholder to have an ID in the UI until a remote mutations returns the real id.


Isn't that what I'm doing? I'm providing the form with a placeholder :note/id until the remote returns a real id.

Björn Ebbinghaus17:12:14

I myself use a singleton ident :ident (fn [] [:form :new-note]) or hooks.

(defsc NoteForm [this {:note/keys [text]}]
  {:query [:note/text]
   :initial-state {:note/text ""}
   :ident (fn [] [:form :new-note]
  (dom/form {:onSubmit 
              (fn [e]
                (evt/prevent-default! e)
                (comp/transact! this 
                  [(save-note #:note{:id (tempid/tempid) :text text})
      {:type "text"
       :value text
       :onChange #(m/set-string! this :note/text :event %)})
    (dom/button {:type "submit"})))

Björn Ebbinghaus17:12:30

I have to admit, that I do not use the form-state support. I don’t have big forms with non-empty initial states. As I said you also could use hooks:

(defsc NoteForm [this _]
  {:use-hooks? true}
  (let [[text set-text!] (hooks/use-state "")
          (fn [e]
            (evt/prevent-default! e)
            (comp/transact! this
              [(save-note #:note{:id (tempid/tempid) :text text})]))
        handle-text-change (hooks/use-callback #(set-text! (evt/target-value %)))]
      {:onSubmit handle-submit}
        {:type     "text"
         :value    text
         :onChange handle-text-change})
      (dom/button {:type "submit"} "Submit"))))

Björn Ebbinghaus17:12:10

And I wish Slack had some sort of code highlighting…


It does when you post a snippet and select the language, but there's no highlighting when you just use a preformatted code block (three backticks).

Björn Ebbinghaus17:12:59

Hm. ok. For work I use Mattermost and there I just can write three backticks and then the name of the language. Just like Markdown.


I don't want to go down the hooks route just yet. My objective should be possible without using hooks, and hooks are fairly new to me. I can get my note to save, and I don't actually think I need form state support yet, but why does it keep complaining that my form isn't normalized?


> Just like Markdown. Yeah, would be nice to get that in Slack.


The FORM NOT NORMALIZED error comes up in by onBlur where I call fs/mark-complete!:


I guess fs/mark-complete! is a form state function so I have to fs/add-form-config* prior to calling that. But m-c! is in my onBlur, so I'm not sure where I could have added the form config prior to that. I have the fs/form-config-join in my query, the form component has an ident... what am I missing here?

Björn Ebbinghaus17:12:39

Hm… What does your DB look like? You get FORM NOT NORMALIZED when there insn’t an ident in (conj entity-ident ::fs/config)

Björn Ebbinghaus17:12:16

:note-form :singleton ::fs/config

Björn Ebbinghaus17:12:23

How do you use NoteForm ?


I just compose it in at the root

Björn Ebbinghaus17:12:18

You don’t call (comp/get-initial-state NoteForm) in Roots initial-state


No, but doing so still doesn't get rid of the normalization warning.


Same result


Actually, I think that using initial-state in template mode, :initial-state {:root/note-form {}} is equivalent to :inital-state (fn [] {:root/note-form (comp/get-initial-state NoteForm}).


When I call (comp/get-initial-state Root) after refreshing with my template-mode code in place, the result is:

{:root/note-form {:note/text ""}, :root/note-list {}}

Björn Ebbinghaus17:12:11

I am not really sure how this all works…

Björn Ebbinghaus17:12:37

There shouldn’t be this in your state… 😕

:com.fulcrologic.fulcro.algorithms.form-state/complete? #{:note/text}

Björn Ebbinghaus17:12:08

Well it should. But not under your ident..

Björn Ebbinghaus17:12:19

I have no idea what is going on. Sorry


All good. Thanks very much for trying to help out. I'm not ready to admit defeat just yet, going to keep at it.


HI, @U01E3601UJW — I know the feeling! I put 14 hours into Pathom adapter around Vimeo (for just two API calls), and my first 4 hour chunk into Fulcro RAD. They both seem to require understanding a vast surface area of machinery, (and corresponding scaffolding that must be correct) for things to work. My impressions so far: the promised payoff seems very high, but at least for me, it requires lots of global understanding of the system, even to do small things. I’m hoping that key understandings will come more frequently after these initial hurdles. 🙂 (I’m watching this thread closely, because I’m hoping to be tackling this same problem you’re describing here hopefully in a couple of days! 🙂


@U6VPZS1EK thanks, glad to know I'm not alone in having some troubles ramping up. I have the same impressions as yourself. I've read much of the book, but while I can see the beauty in Fulcro's approach, I'm not yet able to command it. I mostly chalk up the difficulty to how different the concepts are from the frameworks I'm used to. I also think (hope) that once I get over the learning curve a bit, the payoffs will be there. Fulcro has not been easy to learn, but the strength of the community is very supportive and encouraging. I'll ping you when I figure out the solution to my problem 🙂


For sure. I’ve watched the Fulcro RAD video twice, and certain portions numerous times, squinting at what is being typed. FWIW, Pathom was even more difficult, because it seems like it usually benefits from Fuclro doing all the set up work! 🙂 But I had to watch a bunch of demo videos just to figure out what was going on. I felt like this. 🙂


lol yes, that's pretty much how it feels watching pros do their thing and trying to replication. I was actually thinking maybe it would be easier to just try playing around with Pathom on its own, but sounds like that might not be the case.


@tony.kay I loved your interviews with @jacek.schae, as well as those of @wilkerlucio, who helped me have my “aha” moment — after listening to all of them, as well as watching hours of your two Fulcro RAD videos, I’m ready to take the plunge on my first Fulcro app! This is after spending over 10 hours getting my first couple of Pathom queries working with a set of Vimeo APIs I built (thx to @wilkerlucio and @pithyless for the help in #pathom!) I’ve spent about four hours yesterday playing with the Fulcro RAD demo, and managed to add my Datomic Cloud instance, and even managed to get my first queries against it working..

(let [db (d/db (:video datomic-connections))]
    (d/q '[:find (pull ?s [*])
           [?s :session/title _]] db))
But I’m having trouble finding a quick way to determine whether my first session resolver is actually working. I know that I am somehow able to in my REPL able to type something like (parser/parser env [:sessions/all-sessions]) , and have it give me some feedback on whether the resolvers and underlying scaffolding is working… (Without having to build the UI elements first.) But I can’t get get my head around what to call…. Can anyone help me? (In the Fulcro3 docs, there was mention of the following, that seems to fulfill this need — but I just couldn’t figure out what the 2nd or 3rd arguments were, or how to create from from RAD.)
dev:cljs.user=> (fdn/db->tree [{:friends [:list/label]}] (comp/get-initial-state app.ui/Root {}) {})
{:friends {:list/label "Friends"}}
PS: Kudos to you and @wilkerlucio on the amazing docs for both Fulcro, Fulcro RAD, and Pathom. I’m taking notes on where I’m stumbling, and will write it up, along with some suggestion of additional docs that might help with initial onboarding experience. And thx for showing that there’s a better way than writing queries in three different places! (e.g., CLJS client, CLJ server endpoint, and the actual Datomic query! I found it annoying before, but after watching your videos, it now seems utterly intolerable! 🙂 And that’s certainly what’s motivated the hours I’ve put into trying to get my first app running — the long term benefit seems enormous.) I’ve published my work so far in a forked branch — you can see me currently struggling to call the resolvers here:

❤️ 21

PS: @U01E3601UJW: any chance you’ve figured out how to call the resolvers manually from the REPL yet? 🙂


@U6VPZS1EK I think so... I don't call them directly, but you can pass the query to your api-parser like shown at the very bottom of this section


Funny! I had seen that last night, and took a screenshot of it… It looks like what I need, but in RAD, I think it’s no longer exposed. Going off searching for it! Thx!


I haven't yet started experimenting with RAD. I figure it's better to learn Fulcro proper if I'm committed to getting good at this stuff. I'm usually a bottom-up learner anyway.


…a great idea. I think I’m going to restart using the Fulcro template. I’ll keep you posted, especially when I get queries working and tackle creating new entities!


PS: maybe after we get a little further, wanna collaborate on a “experience report of trying to use Fulcro” — it would be additional motivation for me to make sure I actually write it all down.

Jakub Holý (HolyJak)23:12:06

I would recommend sticking with RAD. I don't think something can be "not exposed" there, it only adds to Fulcro, removes nothing. Essentially it is Fulcro + a library. You can call a resolver certainly, look at it - it is just a map that includes the fn you want to call. But I would call the whole parser normally, you just need to figure out the env. In my case the only thing of consequence in it is the DB connection pool, which the development ns can give me. Even if you lack app.parser/api-parser, you have a pathom parser somewhere. Yes, here is it

Jakub Holý (HolyJak)23:12:37

But you say that you are able to call parser/parser for the sessions query, right? Any reason why you want to call the resolver directly rather than through the parser? It is just a fn as I wrote so you certainly can do that easily...

Jakub Holý (HolyJak)23:12:09

(Aside: thank you so much for the Phoenix Project and your Love letter to Clojure, I enjoyed both tremendously and recommend them widely. Some P. P. characters gave nicknames to some of our colleagues (sometimes just in private conversations though :))


@U0522TWDA Thanks so much for the kind words! And I’ve very much enjoyed your blog posts over the years — I remember studying your dev REPL posts, and the one on incanter/gnuplot (long before I ventured to use oz!) Okay, I love the encouragement to stay in RAD — I’ll do some poking around tonight inside of it… Can you confirm that I’m thinking about this the right way, and have the arguments clear in my head?

(com.example.components.parser/parser {env} eql-query)
I think {env} will be com.example.components.config/config and eql-query will look something like [{[:vimeo.user/id 118038002] [{:vimeo.album-list/data [:vimeo.album/uri]}]}] Wow, if I could get this part running, I’ll feel AMAZING! 🙂


OMG. It worked. 🤯🤯🙏🙏 > (parser com.example.components.config/config > {:address/id 1}) > => {[:address/id 1] #:address{:id 1}}

Jakub Holý (HolyJak)10:12:05

awesome! And thank you for your kind words!!! (Funny to see we are both roaming the land of Oz :))

Jakub Holý (HolyJak)10:12:54

I invoke the parser like this

    [{[:bill-run/id 123]
      [{:bill-run/invoices-too-long [:invoice/id]}]}])
where the development calls returns
this is obviously for a SQL DB, Datomic would be different but in the same spirit.


@U6VPZS1EK on env. The env is anything you want to make available to the resolvers. If you’ve got the server spun up and are using the parser component, then it will add a number of things to the env via plugins. You can start env off with anything “extra” you want to put in. I think the demo maybe throws in the Ring request and some other per-call kinds of things.