Fork me on GitHub
Jakub Holý (HolyJak)11:12:26

In RAD, why is a :dev dependency? I would expect it to be a standard dependency, so I am obviously missing something important...?

Chris O’Donnell12:12:58

It should only be used by cljs, and so it should only needed at build time.

👍 3
🤯 3

Posting a brief update — I had the privilege of having my third pairing session with @holyjak yesterday using Fulcro RAD, and quite frankly, I’m in awe of what we got done in under an hour (after updating dependencies, getting Fulcro Inspector working, etc): I learned how to build a column-formatter for URLs, customer formatter for an enumerated type, and got first mutation working to allow server-side calls. What particularly stuns me is how mutations work: and how much of the work of getting data from the CLJS client and CLJ backend, and back again, is all just taken care of for you — on the client, there’s no need for cljs-http or async state management, marshaling of data on client and server, writing argument processing in ring/compojure.. To see our first mutation running round-trip was… awe inspiring, jaw dropping…. I put this experience up there with the top jaw-dropping moments I’ve had in my career: first Heroku push, first time using Ruby on Rails… It’s been a very challenging learning curve, and frankly, I’m in awe of how people are learning this without a significant amount of hands-on help from someone like Jakub. But, without doubt, there’s an incredible power and leverage that Fulcro RAD affords, which was obviously something I was hoping for, given the 40+ hours of time I’ve put into this so far And I know I’ve just barely scratched the surface of understanding how to do anything. Amazing work, @tony.kay! (Okay, back to trying to build a subform to associate Tags with Sessions! 🙂

🎉 15

@tony.kay I’m working off of the datomic-cloud branch of fulcro-rad-demo. I’m merging in your updates from main — if you’re interested, I can put together a PR with those changes merged in… Let me know…


sure, I just don’t have time to test stuff, so please make sure it is solid and the instructions are good, or else it spawns questions 🙂


and glad you like it all…RAD is a bit unpolished and underdocumented, but I only have so much time


Also, working off of main, I’m getting these warnings/errors in the console — are these problems? (The line-items reports no longer work in datomic-cloud branch, so I’m trying to get those working again…). Specifically I’m wondering about the UUID formatter — would that be easy to fix?


The error is from putting a UUID on a form. the SUI plugin does not have an input renderer for that data type


Is the problem just aesthetic, or does it actually prevent any demo functionality?


no idea 🙂 Been a while since I looked


I wasn’t aware I had used a UUID in a form field

Jakub Holý (HolyJak)20:12:34

Not form but a report I think. The invoice/id in the AccountInvoices report. I guess sending a PR to RAD adding a default formatter for uuid would be best


Hi! When reading about computed props in the book (F2) it says we can’t put code in the database, is there a section in the documentation explaining that better? Because no errors are raised when you insert a function in the DB so I would like to understand what are the consequences if someone does put code in it

Jakub Holý (HolyJak)20:12:27

At least Fulcro Inspect will suffer as DB needs to be serialized to send to it I believe.


I’m not sure but I think it will show as an unknown transit type


Maybe it is related to the diff


As you’ve seen, you can do it. But here’s where it will cause you problems (which are the same problems for putting other code artifacts in the db): 1. If you make such a tx full-stack the fn will cause exception (live code cannot be serialized) 2. Inspect won’t understand them, or be able to display much sane 3. It breaks the overall conceptual design: operations are data in fulcro. Save a muation symbol and params to state instead. This leads to conceptual complexity in your program that is usually quite easy to avoid. 4. If you wanted a support viewer (serialize db to devs so they can diagnose errors from the field), the such values will make db not make sense. That said, F2 is harder to work with than 3 in certain circumstances (see Incubator in fulcro-legacy for some of the add-ons that inspired F3). Sometimes you just want a callback, but Fulcro does encourage you to formalize those into mutations..and a mutation (symbol + params) is something you can put in state.

thanks2 3
Gleb Posobin20:12:11

Hi, I am just getting started with fulcro and trying to adapt fulcro-template to what I need. I want to pass an auth token to the server in a header on every request (if the user is logged in), but I am having trouble understanding how to make the fulcro-http-remote read the auth token from the db, and attach to every request.

Jakub Holý (HolyJak)21:12:16

Hi, welcome! Nowadays it is perhaps better to start with RAD, which is Fulcro plus an extra library that you don't need to use. The demo is perhaps better maintained than fulcro-template. I am guessing here but perhaps you could leverage the :request-middleware option of the remote? And you have your global app which you can use to get at the DB and thus the token. Or you can just store the token in an atom...

Gleb Posobin21:12:45

Hmm, weird, I looked at rad template and it seemed much less fully-featured.

Gleb Posobin21:12:30

Ah, so I can access the app db externally.

Jakub Holý (HolyJak)21:12:53

Yes. And look at wrap-csrf-token in the ...http-remote ns as an inspiration

Gleb Posobin21:12:06

Yeah, I saw that. Thank you!

👍 3
Gleb Posobin21:12:51

One thing that made me choose fulcro template vs fulcro rad template was that the latter doesn't have workspaces.

👍 3
Jakub Holý (HolyJak)21:12:59

Also, there is nothing that forces you to store the token in the DB, you could just put it in an atom. Everything is possible 🙂

Gleb Posobin21:12:05

Yeah, but I want the whole state to be in one place.

Gleb Posobin21:12:33

Though I guess I am influenced by re-frame in this regard.

Gleb Posobin21:12:49

Also the fact that RAD is in an alpha was off-putting. What are the advantages of RAD?

Gleb Posobin21:12:39

And that RAD has its own book: the fulcro book itself is long enough and hard to understand so far, and here I need to read another one! Scary 😃


Short answer: Make a ns for an atom to hold your app. This is necessary to avoid circular refs (clj limitation). Then, when you create your app, make sure you initialize that atom. Now your app is globally accessible. Write your remote to grab the app from the atom and do whatever it needs to do.

👍 3

You could also make an atom just for the auth stuff (localized to remote) and have the client-side request/response middleware do the work of seeing the auth response, and sending it out on requests.


lots of ways 🙂

Chris O’Donnell23:12:41

If you need to pass a bearer token instead of using session auth, I wrote a blog post describing one way to do that:

Tyler Nisonoff21:12:58

I have a fair amount of db-persisted RAD attributes that I want to display defaults for on the frontend. I’m trying to a void a bunch of (or attr default-val) throughout the UI code. I’m currently experimenting with an approach where, say I have a :person/age field that I want to default to “10”. I’ll create a db-persisted attribute, :person/age* , and a virtual attribute, :person/age that reads :person/age* from the db, checks if a stored value exists, and returns 10 otherwise. Then on the frontend i always use :person/age in forms / views. And to ensure we can write new values, I rename any occurrences of :person/age -> :person/age* in my save middleware. I’m curious if others run into this pattern, or see any obvious pitfalls with this approach?

Tyler Nisonoff21:12:44

And i’ll add that for my use-cases, I wouldn’t want to necessarily just write the default value to the db the first time the entity is created. I have certain time-based properties that, if unspecified, I want the default to dynamically change over time.

Jakub Holý (HolyJak)21:12:45

Why not just add form-options/default-value to the attribute and make sure you formatters/... use it, if they dont out of the box?

Jakub Holý (HolyJak)21:12:38

in save-middleware you could check value != default and skip safe if it does =

Tyler Nisonoff21:12:13

i frequently am using the value in business logic, say calculating results and such.

Tyler Nisonoff21:12:00

I could use formatters / or have a helper function that i remember call every time, but then im constantly deconstructing the props and needing to remember to wrap the value in a function


@U016TR1B18E the approach I recommend is basically what you’ve discovered: Leverage pathom, make it a virtual attribute that behaves the way you want. Leverage save middleware to deal with the write complications that this incurs.

😁 3

that makes it super clean. The “hacks” are very well-localized. You could go one step further if this is common: Attributes are extensible, remember? 🙂 You can invent a new nsed key that you add to attributes where you want this sort of behavior. Value can be a lambda or a value (use !?). Then just copy the resolver generator code (it’s pretty small) and augment it to support your new general use-case. Same for write middleware.

Tyler Nisonoff22:12:52

Ah yeah thats a neat idea, thanks!


Welcome. The intended design of RAD is that you mostly outgrow it, but adopt the principles 🙂

👍 3
Jakub Holý (HolyJak)21:12:37

@wilkerlucio hi, it seems that Index Explorer in Fulcro Insepct is not working in RAD. If you have by a chance fixed that for yourself, please share the fix 🙂 (I have defined an index-explorer resolver and added it to the parser but it fails because transit cannot handle values such as Fulcro attributes, which I assume are included as resolver functions in the index.) I do not intend to bother, just checking if a solution exists already 🙂


hello, I've not tried with RAD, but if you can remove the fulcro attributes stuff from the index, it should work fine

Michael W21:12:06

(pc/defresolver index-explorer [{::pc/keys [indexes]} _]                                                                
  {::pc/input  #{:com.wsscode.pathom.viz.index-explorer/id}
   ::pc/output [:com.wsscode.pathom.viz.index-explorer/index]}
  {:com.wsscode.pathom.viz.index-explorer/index (com.wsscode.pathom.core/transduce-maps
                                                  (remove (comp #{::pc/resolve ::pc/mutate} key)) indexes)})

Michael W21:12:57

Someone gave me that to strip the resolve and mutate functions, I don't remember who


I'm still in the process of trying to grok fulcro's design, mainly how the db should be structured and manipulated • should all data in the db be normalized? Are there use cases where data shouldn't be normalized? • I see :db/id and :my-entity/id in the docs. is :db/id special? when is each appropriate? • If I have a component and want to test just that one component. Since the normalized db with the data for that component would be nested, is the right way to set that up to wrap the component I would like to test in a Root component that grabs a single entity that matches the test component and passes it to the test component?

Jakub Holý (HolyJak)22:12:46

1. Mostly yes but not necessarily. You don't need to normalize if you know the entity will not be used from multiple places. (Simply do not give it an :ident -> no normalization). 2. No, :db/id is not special in any way. 3. What kind of testing do you meant? Manual? Automated? Look at for displaying components in isolation


regarding 3) I guess manual testing, but mainly as a way to support repl driven development


my intention is to be able to start with a blank component and build it while the app is running


workspaces looks really interesting. thanks for the pointer! my current goal is trying to make a desktop app with Fulcro for state management and my project, membrane, for graphics so I'll have to play with it to see what I can reuse.

Jakub Holý (HolyJak)22:12:22

Ok! Yes, you could put the component under Root and use an ident join to grab the entity - Root's :query [.. {[:person/id 123] [:person/name ..]}] and in the body something like (ui-my-component (get props [:person/id 123])) could work (notice it is get, not get-in, same as for df load markers)


perfect! that's what I figured, but wasn't sure. It seems like it would be possible to wrap that behavior in a function that creates an anonymous "Root" component for just that purpose


as an example:

(defsc Checkbox [this {:checkbox/keys [id checked?]}]
  {:ident :checkbox/id
   :query [:checkbox/id :checkbox/checked?]}

   (fn [_]
     [`(toggle-checkbox {:checkbox/id ~id})])
   (ui/checkbox checked?)))

(def test-app
  ;; pop up a window with just a checkbox
  (quick-view! Checkbox {:checkbox/id 1
                         :checkbox/checked? true}))
I've been using this, but it currently creates a non-normalized db which breaks things when I go to a normalized db. I think I can fix quick-view! to have my cake and eat it too


Please (please) read an understand as much about the combo of query/ident/initial-state as you can. This video, which is very outdated (Untangled/Om Next days), is also perhaps useful for understanding the db ideas:


You should normalize anything you plan to modify, but there is no hard requirement. A “big blob of data” is allowed.


I watched that video last night, but I'll try it again


I personally normalize everything


except things like dumps of data that are just like points of a graph or something


report data, you know


non-editable, and much faster to just keep in a bundle


The other videos in the F3 playlist cover a lot of ground


I think I understand some of the concepts, but I struggle with groking the operations: merge!, merge*, db->tree, and merge-component


I can't find any examples that just show example inputs and outputs


ignore merge!, merge*


I never use those…treat them as internal


db->tree is essentially Datomic pull, for Fulcro basically (different API, but that’s the purpose: state + query => tree)


Think of merge-component as a non-network version of load!


The model is really really really simple. You have a tree of data and a tree of components (with queries). The two match each other in shape. merging Root query with Root data tree makes a normalized database. merge-component is how you add a “fragment” to that tree. load does network I/O, then uses merge-component to add fragments to that tree.


there are several examples in the tutorial that modify the state of the db directly. I thought understanding what merge* does would help understand those examples


initial state is that initial data tree that is used to make the db


mutations have optimistic actions that can do anything to the db…but they are all technically graph edits.


The db format is trivial. top-level “root” keys that have any meaning you want, top-level keys (you define with the first member of ident functions) that represent tables indexed by id, entries in those tables, and “idents” (lookup refs) that are used as pointers to join it all together.


so in the case of building a desktop app, should all of the edits be done in actions since there is no network?


that's what I've been doing


some utilities like the stuff in normalized-state ns might be better reading 🙂

👍 3

Yes, if you mean “mutation actions”

👍 3

it leaves open the possibility that you might actually want to talk on a network via Fulcro in some apps :)


reading the source of merge and db->tree is like reading the source of postgresql to understand what a table is

😆 6

if you succeed, you’ll darn well know what a table is, but it sure is a hell of a way to go about it 🙂


makes me feel better about not understanding everything then


Is there a good example of a reusable form control in fulcro. Something like a date picker or custom drop down menu or something like that? I'm working on using fulcro for a desktop app, but don't want to rely on stateful OO components like textareas from Swing/JavaFx, etc. It would be possible to hide that state behind an object, like the DOM does, but I would prefer to use fulcro for all state management including cursors, focus, etc.