Fork me on GitHub

So, the RAD Routing... what do I do if I can't import the namespace of whatever I want to route to? It seems like just giving the fully qualified name (like com.example.accountpages/Overview ) in quoted form does not work


registry-key->class in com.fulcrologic.fulcro.components


Actually not sure that answers the question now..


Ahh yes, that's it 👍 Thanks!


Also I have not found out what exactly triggers the ::uism/started event in a state machine state. It seems it only gets triggered when I make shadow-cljs re-send the compiled js code, but never on initial page load

Jakub Holý (HolyJak)10:06:18

Speaking of RAD? I think it is sent when you route to the report/form. I remember I had to start it manually when I wanted to use a report outside of a router.


No, independent of RAD, but I am using RAD. I do manually start the uism for authentication stuff, but right when loading a page it will stay at :initial until I either click a link or load new code


Any hints would be nice, because I spent way too much time already with this... Pretty sure I did something wrong, because I'm not the only one using this. I use the state machine from the fulcro template


started is sent on calls to begin!, which is done in RAD on routing will-enter (i.e. you have to trigger a route). There is also a function you can call to start the report or form in each respective ns if you are not routing to it


Oh well then I think it should work? I have the call to uism/begin! in the init function in client.cljs, just like it is in the fulcro template as well as the RAD demo. But what is currently happening is that it is "stuck" at the :initial state until I either update the app by saving a file, or I click something that triggers routing. Same happens after login with the :state/checking-session state. It seems it only goes one step each time.


“one step each time”…explain


When the page loads it is at :initial and stays there. As soon as I update, in this case by clicking on my Login (separate page), it goes to :state/logged-out . When I log in, it loads the next page and then is at :state/checking-session, until I click something else at which point it goes to :state/logged-in. As soon as I refresh the page with F5, it is back at being stuck at :initial until I update.


(using the state machine from fulcro-template)


so, how are you seeing current state? Inspect or looking at the atom


(trying to split between possible bug in Inspect)


By having a (dom/p "Current UISM state: " (str (uism/get-active-state this ::authorization/session))) on the page. Quite crude, but gives the same result as logging it


OK, but that could be stale due to a rendering issue…why not use Inspect???


Also, when refreshing on the login page, I get an error when trying to login because the uism is in the wrong state


As far as “authority” on state goes: Using a REPL to inspect the app ::app/state-atom is always right. Inspect is most likely right as well (it give up if it detects out of sync and goes blank). Rendering is not reliable, since React can optimize away an update


so, first, please examine state with either REPL or Inspect (preferred)


Where in Inspect is that information?


DB. In the c.f.f.u/asm-id table


under whatever ID you gave the SM


the state machines are just normalized data


The way I even noticed this was because my login form would break when accessed directly. A look in the console confirmed my suspicions because there was an error from uism that there is no such event in state :initial


Hmm okay I figured it out now... No idea why that would cause such a thing, but I didn't use the same routing function everywhere. Most places were RAD's route-to! but I still had normal Fulcro's matching as well.

Jakub Holý (HolyJak)10:06:01

@tony.kay I run again into the issue where a report is empty after hard-reload of a page, despite the data being fetched. I'd love to help fix this but I need your advice on how to troubleshoot it. Facts so far (90% confidence :)): • It happens for the second report and not the first one under the same router • It happens nearly always in release build, rarely in dev build • Fulcro can see the data - (->> (db->tree (comp/get-query SubscriberList) [::report/id :myns/SubscriberList] (app/current-state APP)) :ui/current-rows count) returns > 0 • It doesn't seem to be a rendering issue - I use keyframe-render2 and (app/render! app {:force-root? true}) has no effect Where should I look? 🙏


Um. No console errors?


Sounds like a timing issue to me. In dev there is a lot of stuff adding overhead, so things move more slowly. In release, they are much much faster.


also not sure what you mean by a hard reload of the page…you mean: if HTML5 routing is used to get there? Or do you mean, you reload, then click to go there?


there is only “one page” after all 😉


@U0522TWDA you still around?


Also, is this a “default route”? It could be something related to that as opposed to anything at all to do with the report.

Jakub Holý (HolyJak)15:06:05

I am here. HArd reload is Cmd+Shift+R. I am using RAD with its history API / url integration


and is that the default route, or an alternate that was in the URL?


So, are you set up where you can edit the source of RAD itself, like here ?


(including Fulcro)

Jakub Holý (HolyJak)15:06:56

An alternate route - that is what I meant that it works for the first report (route) but fails in this way for the second report=route


ok, but only on HTML5 routing from startup


that’s when it fails?


I don’t mind helping, but I would like to be efficient. If you know when you’re available in realtime, let me knwo

Jakub Holý (HolyJak)15:06:53

My default page (no route in URL) is an empty page. when I type the url /org/123 then the first report is displayed, with data. I click a link there to go to /org/123/subscribers , which replaces the report with a list of subscribers, which works. I Cmd+Shift+R on this page - and now the subscribers list = report is empty.

Jakub Holý (HolyJak)15:06:08

I am available in realtime now


ok, and when you look at the state, it seems all linked up…you said current rows is populated


meaning the report’s state machine did the right thing.


console messages?

Jakub Holý (HolyJak)15:06:19

I.e. hard-reload on the non-default route /org/123 works but not on /org/123/subscribers . Yes, the state looks ok.


ok, to get release-like performance, but a dev experience, I’d build like so:


1. in the shell set TIMBRE_LEVEL=:info 2. Disable guardrails

Jakub Holý (HolyJak)15:06:24

No special console messages except for the warning The actual inputs (:bill-run/id :br-subscriber/subscriber-id) on minbedrift.ui.kostnadsdeling.ui/SubscriberList do not match the declared layout: [[:br-subscriber/subscriber-id]] (I have a param I dont want the users to set themselves)


that might get it to misbehave in devel while you still ahve tooling


I’m assuming from earlier messages that you ahve a hard time making it not work in devel

Jakub Holý (HolyJak)15:06:04

I have right now open the page in dev where the error did exhibit so I can look at any state etc to troubleshooti t.


ok, and in that case, still no console errors?


Also, you can use production react js in development as well, using shadow cljs resolve settings (like shown here:

Jakub Holý (HolyJak)15:06:53

Actually, I do manage to replicate it in dev now it seems. No errors, only the 3 warnnings about "The actual inputs .."


ok, and when you look at the report’s state, current-rows has data?


I’d also look at the other bits of state: is pagination enabled? Does the pagination stuff look right? Is the row data “sane”? Are you using row transforms?

Jakub Holý (HolyJak)15:06:09

No transform, no pagination, data looks ok. According to db->tree and Fulcro Inspect it has data

Jakub Holý (HolyJak)15:06:36

I have copied the state DB from the case of empty report x correct report and am looking at their diff

Jakub Holý (HolyJak)15:06:14

Here is a significant difference: in the OK report I have

in the BAD case I have
(where DetailsDisplayRouter is the parent router of the first/second report and LatestBillRunList is the "first" report and SubscriberList is the "second" report)


so, route looks wrong


is LatestBillRunList your default route?


The transaction list in Inspect will have the sequence of things that led to that

Jakub Holý (HolyJak)15:06:55

Yes, the current route looks wrong. The UI does display the expected SubscriberList. I will look at the Transactions history to try understand it...

Jakub Holý (HolyJak)15:06:46

What should I look for?


so, each router is a state machine


do you have nested routers?


you’re looking for which state machine was sent which events, in which order


probably would help to make a numbered diagram


At some point I really want to change the dynamic router and drop the deferred route support. It sounded like a good idea, but I think its the source of a lot of headaches.

👍 4

It turns routing into an async behavior, which is a slight bit of insanity

Jakub Holý (HolyJak)16:06:27

Here is the event list, but it doesn't tell me anything... I have three nested routers: Root > Org > DetailsDisplay (parent of LatestBillRunList [default] and SubscriberList) 1. Begin RootRouter 2. Begin OrgRouter 3. Begin DetailsDisplayRouter 4. Waiting OrgRouter 5. Waiting DetailsDisplayRouter 6. [load data for OrgRouter target] 7. Begin SubscriberList 8. df/target-ready SubscriberList 9. [load SubscriberList data] 10. ready! DetailsDisplayRouter - here its current-route becomes [nil :myns/SubscriberList] (was [nil :myns/LatestBillRunList] ) state after has :current/route [:myns/SubscriberList] 11. df/target-ready [org "123"] ( OrgRouter target) - weirdly, state before has DetailsDisplayRouter :current-route [:myns/LatestBillRunList] ?! 12. timeout! OrgRouter 13. :event/loaded SubscriberList 14. ready! OrgRouter

Jakub Holý (HolyJak)16:06:50

Maybe I am the cause of the issue, in this code:

(defrouter DetailsDisplayRouter [this {:keys [current-state]}]
  {:router-targets [LatestBillRunList SubscriberList ChargeList Empty]}
  (case current-state
    :pending (dom/p "Loading...")
    :failed (dom/p "Failed loading details")
    (do ;; :initial / nil: no route selected yet, route to the latest bill runs & show nothing in the meantime
      (routing/route-to! this LatestBillRunList (comp/get-computed this))
Here I explicitly route to the default route when no route is selected, because I experienced problems when the route wasn't explicit. Perhaps I do it wrong and screw things up?


so OrgRouter resolved after it’s child


oh…yeah, that’s not good…never side-effect in rendering

Jakub Holý (HolyJak)16:06:14

Ok, I won't. But removing it has no effect on the problem. Yes, OrgRouter resolved after its child.

Jakub Holý (HolyJak)16:06:17

Though I did the same thing in the OrgRouter (if no route, route explicitly to the default target). I removed that and now it seems to be working just fine...

Jakub Holý (HolyJak)16:06:53

Or actually not, 4th reload ended up with the problem again

Jakub Holý (HolyJak)16:06:30

OrgRouter is still the last one to get ready!

Jakub Holý (HolyJak)16:06:56

BTW the URL contains the report param I call here org , which both OrgRouter and DetailsDisplayRouter use. I guess that, as the url is processed by the routing system, that is why OrgRouter is forced to redo its thing. Why it finishes last I dont know.

Jakub Holý (HolyJak)16:06:16

Since OrgRouter is :routed last, and its target has LatestBillRunList as its default target, could it be somehow that it overrides the current route of its child's router (DetailsD.R.) to be its initial/default value?

Jakub Holý (HolyJak)16:06:58

@tony.kay I have discovered one really weird thing. In the transaction list above, the state after at point 10 is :current/route [:myns/SubscriberList] yet the state before of the next point 11 is :current-route [:myns/LatestBillRunList] How could this be possible that the state changes between two consecutive transactions?!


I’m concerned about possible deferred routing stuff. The way that all has to work is that each layer of the route could defer, and that (I think) delays the routing process. It’s too much for me to even thing about at the moment…other things going on. The side-effecting in render is bad…you should definitely not do that. Otherwise, not sure what might be wrong

Jakub Holý (HolyJak)19:06:08

Yeah, I have removed the side-effecting. Any tips for troubleshooting how can the current-route change between two transactions and what caused it?


hm…having trouble figuring that out myself. I mean: the state machine itself is a big mutation


so, triggering an event on it causes state change


but in-between, unless you’re doing something to swap on atom


should not happen

Jakub Holý (HolyJak)17:06:29

FYI I have add-watchto the state atom and the swap! that screws up the current-route (changing it back to the default target) also seems to set the data load! -ed by the middle router's target, i.e. OrgDashboard (I have synchronous RootRouter -> OrgRouter with a deferred route to OrgDashboard -> DetailsDisplayRouter that has targets BillRunsList (default) and SubscribersList (which I expect to see)). Perhaps the effect of the load! and the subsequent :post-mutation dr/target-ready` happen in the same swap! and it is this post mutation that kind of resets the child router - DetailsD.R. - to show its default target again? As noted before, the last transaction is ready! OrgRouter , which would match this fact. Also, when things work out correctly and the SubscriberList is not only showing but also the current-route and has the data, all the transactions are the same except the newly inserted 13: • 12. timeout! OrgRouter • [new] 13. timeout! DetailsDisplayRouter • 14. :event/loaded SubscriberList • 15. ready! OrgRouter

Jakub Holý (HolyJak)13:06:22

@tony.kay what do you think about ☝️ ? Could it be the target-ready postmutation of the parent deferred component that leads to the reset of its router back to the first target (overriding the expected current-route)?


yeah, the timeouts should be causing a state change to display the “route pending” state. If there’s a bug there, you could always up your timeouts on change-route to something large and just not use that feature.

👀 4

What are the advantages of each of these? (Sorry for the handwriting - passenger one a car rn.)


that’s a rather large answer….could write a book


What are important tells that one should be used over the other? In my head I’m imagining that the translation on the server is more favorable, but I’m not sure why I think that.


I’ll try to mention the main ones that come to mind: • Whose CPU does the work? • Who has access to edit the code of the network service? (do you own the server?) • Where might you want to build in caching? Do you want an offline mode?


you also missed a diagram


a layer on both


That was intentional actually. But I can imagine that happening.


the translation layer in the UI also tends to be more abusive to overall low-level I/O resources, since no back-end optimizations can typically be applied.


so you might over fetch to get the result you want…and I/O resources on server are a typical bottleneck; however, with significant client caching you can go the other way easily.


Like I said: book 😄


Gacha. Thanks :)


So, just for context - I have used reframe heavily in the past. In fulcro I sorely miss some of the features, though on the whole it is a good trade. Right now I’m thinking about what it would look like to join the two models.


This came up while thinking about it.


I would love to hear what specific features you’re missing, and how we might address that.


“sorely missing” can also just mean “don’t know how to do it easily in the new tool I’m using”


or it could mean something is really missing


The subscription graph - keeping data in sync is hard with fulcro. I’m sure it’s just me missing out on all the advances in the java swing world (mvc and stuff), but properly invalidating derives data is tough. It is drop dead easy in reframe.


This is w.r.t long calculations that I dont want in the ui rendering.


Ah, so in Reframe how do you deal with the fact that it is a long calculation?


you cache the result, don’t you?


or are you saying Reframe detects “no change” and avoids the calc?


Both. Also, some parts of my app have several dynamic invalidation queues - much easier to detect in re-frame natively. I'm not worried about the formula being in the render for time sake - I can do caching in another layer. But knowing when to re-calculate is hard.


(I did end up coding a system somewhat like re-frame inside fulcro, but it is not nicely integrated. I'm interested in a cleaner integration.)


As a general statement - I think subscription graphs are a very very nice model to maintain simplicity in the ui code.


But reframe also tends to create non normalized databases without careful work, so mutations become very difficult.


Maybe I misunderstood. Yes, the graph in reframe is super nice for caching. You no longer depend on top level changes only - derived middle layers can filter data such that no change is detected and the calculation is halted.


got it…so, I think perhaps using re-frame for that part might be something to consider. I’ve been toying with the idea of a hybrid myself.


I just had an insight on how to make a pretty nice proof of concept without much work. I’ll ping back here once I get a chance to make it. Should be in a week or 2


ok. I mean, the tx processing system of Fulcro is extensible. Ideally, adding such a mechanism would go in there. That would make it a seamless integration


Could also be a system that watches the state atom and feeds back data that way.


Some of the initial thoughts - subscriptions are inherently more powerful than the current system in fulcro for ui derived data. (not that anything in one is not possible in the other, but subscriptions are a good model). The fact that fulcro creates the entire data tree to match the ui is cool, but un-necessary. Each component could side-load it's data from subscriptions. Imagine re-frame having a macro that creates a query. This macro does 3 things: First, it adds the component to fulcro's registry so db->tree works. 2nd, it adds a subscription generator that given the component ident returns a new subscription that has the normalized contents of the db entry. 3rd, same as #2 but returns the denormalized db entry.


I think the way subscriptions in re-frame are handled would actually have to change slightly for this to be fast.


In fact - just reading the re-frame docs I came across this. Kinda how I got myself in trouble the last app I made.


So, there are pros and cons to everything. The joined tree of UI in Fulcro is not necessary: see floating roots. The only feature that currently uses the joined tree (globally) to good effect is Dynamic Router, and that could be done differently; however, note that subscriptions also have a lot of problems and challenges. The joined tree is also used by forms, but that is just a sub-tree…there is no reason, per-se, for that subtree to need to be joined to the parent.


Remember that Reframe is largely concerned with non-distributed concerns: its a front-end library that you can side-effect from…Nothing gets you away from the complications of data loading except a meteor-like db sync (which moves the complexity to a layer you don’t control, and which is really hard to get right while also being scalable).


The “joined UI tree” in Fulcro is an inconvenience that floating roots address for the cases where it actually hurts you. Subscriptions (NOR) floating roots solve the harder problem: When do you load the data? How do you keep it in sync? What if two different components need different subsets of the same normalized data?


And for those cases I would argue that you want that logic to be lightly coupled in a small space with an easy ability to comprehend and trace it. Disconnecting it will create much more difficult problems to solve for a very minor initial convenience.


I would encourage you to learn how to write the thing you’re trying to get with the features Fulcro provides. Get good at that, and then try to design an improvement; otherwise I fear you’re thinking too much about a “feature” than an actual scalable development practice: I’m interested in the latter, and only care about the former when it becomes obvious that it will somehow help with the latter “on the whole”. Developers tend to concentrate too much on convenience first without thinking more long-term.


I love convenience…don’t get me wrong…I just think emphasizing it too early is a big mistake.


“subscriptions are inherently more powerful than the current system in fulcro for ui derived data” is a subjective opinion. They are a feature you’re used to that you happen to like to code that way. They have trade-offs in both directions (some things are easier, some are harder).


Derived UI data is another one where you’re perhaps concentrating on a particular feature without thinking through all of the implications.


Why is it more powerful? What are you comparing, exactly? I’d agree with you that the signal graph system of reframe has more features for derived data that Fulcro has (out of the box). Does that scale well? Do teams reason about it over time on large projects well? Can you trace the code, or do you have to “grep” for keywords to find things? How do you understand what you’re getting? Can your IDE navigate to the definition of what is goign to happen for that subscrition? Do side effects from one affect another? How?


(Please understand these are real questions. I’d love to have more powerful tools that are also scalable on teams. I’m open to debate here, so please don’t think I’m taking an aggressive tone. I struggle with these questions all the time and would love to see us evolve things…I’m just trying to frame the questions and solutions with the constraints I’m interested in) 😄


So, for example, the question “Can your IDE navigate?” can easily be solved with a simple def of a symbol next to the thing (def delete :delete) next to the handler for the :delete operation lets you use that symbol to then get code nav from uses of it.


It requires a bit more developer discipline, but turns a grep into a quick nav, and making a macro that turns it into a convenience gets you both (which is just what defmutation is in Fulcro…a defmethod that is navigable)


same with subscriptions: you can get tool help like “find usages” that are a lot more accurate if it is either a namespaced keyword or a proper sym. Teams need these kinds of things to comprehend the code. If the tool feature doesn’t have that, then it is a problem.


UISM in Fulcro has this problem: it is event-based. It is harder to trace as a result. Fortunately, it is an “enclosed unit”. All of the events are defined in one small code unit. I’ve not yet made macro wrappers for making events to make them more navigable (because SMs are meant to be reusable in complex ways that I have not fully analyzed for making such a macro be a library quality feature). But code I write with UISM has the undesirable property of creating a hard-to-navigate without good discipline, meaning a team will possibly not scale well with that feature.


In practice, I find that UISM is harder to work with in some ways than just plain mutations…but it concentrates a different “global concern” into a unit that is easier to reason about. So, trade-off. I lose some of the goodness of perfectly IDE nagivigable and traceable mutations for a system that instead makes it easier to comprehend the “overall logic” of a subsystem when I’m looking at the SM definition.


Ok, I see where you are coming from. Just a bit of background on me - I've been writing CLJ for 3 years, and am in my 1st year of professional development still. (jr in college, whatever that's worth.) So, I don't really have the background w.r.t. teams that you or most of the people here do. The primary advantage I have here over most of the community is that because I'm in college I have alot of spare time to toy around with ideas. But, that still leaves me with things that I can't really think about in context, having never worked on a large team (I currently manage a project of 3 devs on a fulcro project). I'm always interested in hearing how/why stuff works or doesn't work in large teams, or at long development timescales, or at large production. Back to the discussion here with that in mind: the reason I think that reframe is more powerful in this position is that I can see a full implementation of fulcro's db management easily created within the parameters of re-frame. I don't see a corollary the other direction.


Also, I don't think there is any loss from using the result of the merger, but I could be wrong


So, you’re talking about writing new code to emulate something that already exists. I’m certain I could “easily add” reframes features to Fulcro if we’re talking about writing new code. “I can see a full implementation of” is rather subjective. That said, I cannot say, for sure, which would direction be “easier” to write based on the other as a starting point.


but that probably isn’t a useful direction of discussion 🙂


I'm not sure I follow. Why is new code a problem?


I’m more interested in: what part of “derived UI” is interesting to evolve


New code is not a problem…I’m just saying its a subjective thing to claim you can add the features of A to B more easily that B to A.


I see. The reason I'm most interested in it is that it removes more logic from ui components in an efficient manner. The logic could be removed just by extracting the functions like normal, but then you don't get the advantage of caching and diff/no-diff.


I think you’re claiming that the features of A make adding the features of B easier to it than the other way around…but like I said, I’d rather talk about specifics.


I suppose. That's why I'm tossing a demo together, to see if it is even viable.


ok, but caching is a non-trivial issue. Cache invalidation is very tricky business. You’re going to automate that in a distributed system for people, where loading can happen for arbitrary reasons, and the data can change on servers at any time?


It is always put back into the db, correct? as long as the db is the single source of truth, and no subscriptions depend on anything outside of the db, everything should be fine.


Also consider that what you’re talking about is often just an optimization: is the thing really too slow as functions? In my experience, the answer is “usually not”


so now you’re adding features to a library that require more understanding of the developer, and are possibly harder to trace, in order to optimize somthing that doesn’t need optimization


Now, let me take the other position 😜


If you had that “feature”, then it might be more palatable to beginners coming from Reframe, and lead to more adoption, which would lead to more contributors, a wider library set, and a better overall ecosystem 😄


And I’m sure there are cases where such a system really would be “better” in some objective ways.


That is a benefit. Let me think about it for a bit. I think I have a pretty firm grasp on Fulcro at this point. I'm super interested in how re-frame works, but maybe what you are saying is true - most of the case where I feel a need for it are actually already addressed.


Have you looked at how Fulcro RAD Reports are implemented?


I haven't - that's the one spot that I've not had time to explore much.


I’d be really interested in your take on that. It is probably the best example of exactly this kind of concern


It might have a lot of data; The UI needs to filter it, sort it, paginate it.


Using functions in the UI would be too slow


If you were trying to solve a “real problem”, making something that makes the code of that objectively better would be a great litmus test


It uses a UISM to manage the logic. I think it is a place where the concepts of Reframe might really lead to a better result.


I'll take a look. And does the current version of the rad demo use all of this so I have a usage example?


the RAD demo sales_report stuff


ignore the charts…or not. They are just multiple views of the same data. They don’t really have anything to do with the logic of interest.


BUT, the fact that they need to transform some of the data a bit more might actually be of interest


again…some code cleanup with functions would be fine, and would be fast enough. But I am very interested in what you might come up with.


Here is one more thing. As a general statement - fulcro is too complicated.


I happen to think that website development is also complicated, and that fulcro is close to the actual complication of a complete solution. Most people who don't use it will end up with an ad-hoc system as complicated as fulcro


But, I'm concerned with seeing what can be simplified from a mental model without loss of generality.


“too complicated”…I think perhaps you mean “too hard for the beginner?” The model is actually very very simple. But as you say: the central problem is not trivial and has inherent complexity. Fulcro is trying to be “minimally complex”….and you’re right, I’d love to see more additions that can remove any incidental complexity that it still has.


At the moment, my biggest pain points using it are Clojure(script) more than Fulcro. I’ve been working on some static analysis tools, but I don’t have time to develop the solution. I have some ideas that I think could really help.


Pretty much. I took a solid 1.5 years to learn fulcro to a point where I was happy to start. On and off mind you, but this was spare time learning.


Maybe what is need is a slight update to the material. Part of the complication is the sheer size of the fulcro documentation. I printed it out - runs to 100+ pages. Also, the library itself is huge. It reminds me of the clojure core library - every few days I find something else that is super useful that I never saw before. If I consider the possibility that the major problem with fulcro is not the model, but the small community, I think we could do alot to solve that problem.


(super speculative) Split the docs into 3 parts. a: happy path, simple usage of every part. b: detailed path - more detailed than even the current docs. I've seen many gaps in the current explanations. c: fulcro docstrings. Oddly missing in some spots - I'll look to see what is missing later.


Maybe bring everything into sperate libraries. Core should include ui and mutations, not much else. Add additional libraries for routing, fsm, network, forms... I don't have any idea if this is very useful, but my intuition is that splitting everything into smaller chunks would be a large help.


I'm more than happy to help with any part here if it seems useful.


yeah, we can talk about those various options


I want to create a custom form body in RAD, how do I link the form fields back to the attributes? And how do I tell RAD where to insert subforms?

Jakub Holý (HolyJak)15:06:56

I guess just look at what the default impl does?


Yea it passes the data map around like three libraries, taking it apart and putting it back together until you lose track


See form-state in the Fulcro book. The generated form is exactly what you’d hand-code there.


Every to-one has a join edge in props under the join key


every to-many is the same (but will be a vector instead of a map)


Just render it.


Then use the pre-supplied functions in rad-`form` ns to say when fields change, save, undo, etc.


If you use person/name as a field, then there will be :person/name in props. this factory in the SUI code is the generic expression of what you’d do to make an input that is fully-functional with respect to settings:


but more simply:

(dom/input {:value (:person/name props)
            :onChange (fn [evt] (form/input-changed! {:form-instance this} :person/name (evt/target-value evt))})


something like that


Ahh that helps! I initially already tried to use the functions inside the form/ namespace, but that errored out for me. Turns out I have to put this inside a map with specific prefixes.


All that is missing now is actually being able to save.


Um, form/save!


Ooh it failed because I tried to set the password field... I was wondering why on earth that'd fail in my own form and not in the autogenerated one


Is it btw possible to embed a form as a component? I mean I can embed it, but I think I have to call form/start-form!, although I have not yet figured out what exactly this needs as arguments to function

Jakub Holý (HolyJak)13:06:37

You mean not as a router target? With reports it is possible, I just need to call a similar start fn so I assume the same applies to the form

Jakub Holý (HolyJak)13:06:36

What is unclear about the arguments? The docs seem quite clear to me...


Yes, as in not as a router target, exactly.

👍 4

The docs seem indeed very clear, yes, but I always get errors trying to do that


timbre_support.cljs:80 ERROR [com.fulcrologic.rad.form:659] -  dr/target-ready! was called but there was no router waiting for the target listed:  [:vendor/id …∋id=#uuid "343b0353-cb69-4628-aaf2-820d848f68bd"__hash=-1541908784⊢IEquivIHashIPrintWithWriterjsObject#fulcro/tempid["#uuid "343b0353-cb69-4628-aaf2-820d848f68bd""]TempId]meta This could mean you sent one ident, and indicated ready on another.


This is the error I get no matter what when trying to use form/start-form!

Jakub Holý (HolyJak)17:06:29

Look into the DB for what routers have a pending path, compare


Okay, I have the DB open in Fulcro Inspect ("DB Explorer"). Where do I find pending paths?


It makes sense btw that there is no "pending path", because I don't route to that form. I have that form created like this:

(defsc VendorRegistration [this props]
  {:query []
   :ident               (fn [] [:component/id ::VendorRegistration])
   :route-segment       ["vendor-registration"]}
        (dom/a {:href "#"
                :onClick #(form/create! (comp/any->app this) VendorRegistrationRADForm)}
               "Start form"))
       (let [tid (tempid/tempid)
             regform ((comp/computed-factory VendorRegistrationRADForm {:keyfn :vendor/id}) {:vendor/id tid})]
         (form/start-form! (comp/any->app this) tid VendorRegistrationRADForm)
         (dom/div regform)))))


I also tried calling form/start-form! first, and only create the form after, but that gave the same result.

Jakub Holý (HolyJak)17:06:07

Hm, right. Does start-form trigger dr/target-ready? If not, who does? Go not to the DB but the next tab, DB something, search it for "pending" (or current-route")


could be a bug in start-form. I don’t use it without the routing. open to a PR if you find an issue, but no time to debug myself.


Yes, it triggers dr/target-ready?, though not directly, it is called in the state machine


a target ready should not hurt anything, other than showing a warningin the console (that it was unexpected)


Hmm... it runs asynchronously, right? Is there a way to wait until the UISM is in a specific state?


Okay it seems to initialize eventually as soon as I do something in the form that triggers anything like a form/input-changed!


I just... the more I dig in that, the more awful it gets. I tried to switch around the start-form! and actual element creation, I tried creating the values and give it to the form itself, I tried having a go loop that waits until the state changes to then set my values. It. all. fails. What else can I try, @tony.kay ?


so, you’re just trying to do custom rendering?


you should not be touching anything to do with anything except rendering.


the routing will-enter is the only thing that RAD itself triggers to start the form logic, so if you’re doing that bit the same way (or just using routing) then the logic should be fine.


I'm trying to have it add a form child, and set a ui/ variable, but that'll just error out when I place it in the form body and use form/start-form!. It works fine with form/create!.


from there, the SUI RAD plugin can be used for reference on what to do when rendering. I know it is a bit tough to trace because the controls are in a map, but it obviously works with mechanical generation, so all you have to do for manual creation is follow the patterns in there. Sorry there are not docs yet


add a form child? Well, you still ahve to configure that, and you have to start the form machine. If you want to add one on entry, then that is a little more complex, since the machine is responsible for the initialization


I use the form functions for it. form/add-child!


If you’re trying to do something that complex you are pretty much just writing a new component, and might as well just do it in pure Fulcro land: you’re controlling the rendering and logic…you can still use the load/save mechanisms of rad.


but WHEN do you use those? You cannot use that until the form is running


that is meant for user-triggered action


I see in Fulcro Inspect that the editing state gets activated, but that happens asynchronously, and I can't figure out a way to tell it "hey, run this code after the state machine has entered :state/editing"


"You cannot use that until the form is running" - yes, that is exactly my problem


There is a ::form/machine option that will let you plug in your own (derived) state machine. a sm def is just a map, see UISM docs. Replace the startup with your own logic.


Also remember: RAD is meant to be fast to stand up well-known patterns, not solve all problems. It is also meant to be escaped from when it does not address your case well. In this case, the state machine replacement escape hatch is appropriate. In others it might be just to write your own Fulcro component (which can still leverage the load/save, or even some of the rendering logic).


Well, RAD apparently has given up on automatic redraws of that form's UI... Yea I guess using just the load/save in this one might be the sane option?


it’s either that or digging into the internals until you understand them well enough without docs 🙂