Fork me on GitHub

Maybe I'm exceptionally slow, but this is more or less the example set that I needed for the first 3 months of intermittent learning with fulcro. If this style is helpful to other people, let me know and I'll flesh it out into a blog post.

👍 9

Really it needs a writeup beside it to make full sense, but if you've read the first 3 chapters of the book you should be able to understand everything here. This is just a brain dump of examples I used.

Mr. Savy03:12:14

the stuff in here matches some of what I've done for learning, nice!


I'm interested in your writeup! It's taking me ages to get my head around Fulcro and I need all the help I can get.


@U5P29DSUS — I found this incredibly helpful, and matches what I've been doing over the last couple of days, trying to replicate the "final-form" examples in the video-series repo. if you're slow, I'm as slow as you are. 😂 After 30+ hours, I've finally gotten my first F3 components running with RAD components, and I think I'm starting to understand idents and queries — or at least enough to get some components doing useful things. But my comprehension of idents and queries is probably still only 30% — I screw them up all the time. I would find a blog post incredibly helpful! Thanks for your help and inspiration getting that RAD form wired up earlier today! 🎉

Jakub Holý (HolyJak)08:12:21

Your screwups are incredibly useful, Gene! We can learn from them how to provide better documentation / learning materials / error messages so keep a track of those! 🙏

Jakub Holý (HolyJak)13:12:13

Would it help you folks if we made an example set such as this, perhaps leveraging Workspaces, and comment each example to explain what/why it demonstrates?

👍 3

I like that idea a lot. I’m off work this afternoon, so I’ll probably get the template up. If there is somewhere it is better placed I can move the repo

❤️ 3
Jakub Holý (HolyJak)17:12:51

We could / should make a fulcro-community org on GH..


@U6VPZS1EK haha! I learned about fulcro back in dec 2018... I printed off the full fulcro 2 book. I didn't start building anything meaningful until may this year, so don't feel bad 🙂

Jakub Holý (HolyJak)08:12:45

@tony.kay Where is the source code for the RAD.html book? I wanted to send a PR to fix > * Containers (not yet designed) to drop the (not yet designed) since they are designed now.

Gleb Posobin08:12:34

Say I have a component UserPage, which has properties :user/posts and :user/username, which is also the ident, and I want to download posts incrementally: download 10 at the start, and then load and append 10 more on a click of a button. The problem is that on server side, it is actually resolved through two resolvers: :user/username -> :user/id, and then :user/id -> :user/posts. I added handling of two params in the second resolver, and set this fn as a query for UserPage:

(fn [] [:user/username `({:user/posts ~(comp/get-query UserPost)} {:limit 10 :until nil})])
This works for the initial load: I just do
(df/load! app [:user/username username] ui-user-page)
and the posts and user info get displayed. Now I don't know how to request more posts from the server: I could just copy the whole query and set a larger limit, which is dumb, or I need to somehow df/load! with a similar request but with :until set, extract just the :user/posts, and target them at the proper location with targeting/append-to. How do I do that? Plus, in reality I also have a bunch of other props loaded in UserPage's query, which I also would not want to reload, but eql/focus-subquery doesn't seem to work with parametrized queries. And I can't pass the params in the last argument to df/load! because they are the params for the whole query, which only the first resolver :user/username -> :user/id gets, and the :user/id -> :user/posts doesn't see those params. Maybe I am accessing params in pathom wrong? I am doing (-> env :ast :params).

Jakub Holý (HolyJak)08:12:49 uses a dedicated *Page component to wrap the paginated list, displaying a page at a time. It loads more through this load!

(df/load! app :paginate/items ListItem {:params {:start start :end end}                                              :target [:page/by-number page-number :page/items]}) 
Why is this not applicable in your case? And how does the fact that pathom first needs to go from username to user /id complicate it??

Gleb Posobin08:12:15

It is applicable, but do I have to create a global resolver for every such list then?

Gleb Posobin08:12:48

This seems weird, plus I now have to be conscious about naming it uniquely since if it appears somewhere else, pathom will resolve it.

Gleb Posobin08:12:12

I see that there is load-field!, which does almost the thing I need, I just need to figure out a way to get a handle on the UserPage component instance I am rendering (I am doing the load! before the component is rendered).

Jakub Holý (HolyJak)08:12:38

well Pathom has no built-in support for pagination so yes, you need to make a resolver for each paginated list, or autogenerate those based on attributes or whatever.

Gleb Posobin08:12:29

But this is also more general than pagination, this is required for any query that is nested and needs params.

Jakub Holý (HolyJak)08:12:31

Ah, I see what you meant, you don't want a global resolver, you want to load the user's posts in a paginated way. So you still need a resolver, ie.e. my ☝️ applies, but it needs not be global. The load will be different. And the question you are trying to answer is what the load will look like, right?

Jakub Holý (HolyJak)12:12:27

load-field! looks as a good solution for loading more posts (while the initial, pre-render load! of UserPage can load the initial set of posts. . When exactly do you want to load the additional posts? <> ie do you have the component instance there? BTW why doesn't :focus on load! work? You don't need to use the low level eql/focus transform?! (and if you do, apply it one leve down, not to the whole (my-query my-params) but just the my-query


1. Promote top level query parameters out of ast and into pathom env. See rad plugins around query-params. The main deal is that Fulcro always attaches params to the top level of the query, but sometimes you want them used by nested ones. So an env plugin that puts them there is necessary. 2. Use params on load that specify pagination, possibly targeted (so you could specify pagination for more than one child at one…maybe not needed): :params {:pagination {:posts {:start 2}}} kind of thing… 3. Make resolver pagination aware via query-params in env


Technically EQL supports putting params anywhere, of course, but the load API specifically avoids the issue of anywhere but the “top” because that works in pretty much all the cases with this scheme and avoids the complexity of putting them into the dynamic query of the components at runtime (doable with set-query! by the way). Fulcro 1 and 2 had parameter placeholder support in the queries, but after several years no one had ever used them, so when I went to 3 I dropped it. I would consider re-adding it if a good usage pattern emerged, but the fact is you always need to issue a load to get the next page, and some other logic to integrate that new page, etc….so you end up having a UISM involved anyway, and explicit params are just less work from a practical perspective


i.e. you used to be able to put [:user/name ({:user/posts [:post/id …]} {:page ?page})] in a component query, and then use set-query! to set the ?page parameter to, say, 5. The problem was that now you have a problem. Until you’ve set the param this is a somewhat “invalid” query on a UI component (you can default page to nil at best). So, now you have to have query param defaults in component options, etc. In some ways it is a more elegant solution, but in practice it just hasn’t been necessary, so I dropped maintaining the feature. I don’t have time to revisit it anytime soon, and the way we currently do it works well enough in every circumstance I’ve found. YMMV, of course. Now that I’m revisiting it in my mind it might make sense to just let you set the query params via :params option of load! and ignore it in the UI altogether. That that didn’t occur to me before because this is a vestigial feature from Om Next, which did not have load!.


Now that I’ve said all that, though, this still would not be ideal for your circumstance, because you want to append pages onto a list, not switch to a new page. In that case the query param really makes no sense at all.


So, I’m back to my current design: parameters are orthogonal data that you interpret via your own logic. For “appending a new page to the end” you just need to use what’s provided, probably ideally with a (reusable) UISM


The UISM would be told who the actors are (the parent, and the line item component), which field of the parent is the join, etc. Then all the control logic can live in there, and can be reused for this pattern any time it comes up.

❤️ 3
Gleb Posobin20:12:54

Thank you for the explanations!

Gleb Posobin20:12:06

I have added the query-params->env from RAD and using load-field successfully, now I am trying to understand how to merge correctly so that the list from the response is appended to the local list, and does not replace it. The response is of the form {[:user/username "user"] {:user/posts [...]}}, and the local data is of the same form, but I want to append the posts instead of replacing them. I didn't get your part about "In that case the query param really makes no sense at all": why is that? How should I do it instead? I'll have to merge in the new data somehow even without the query params though.

Gleb Posobin22:12:11

I thought maybe pre-merge would do that, but the posts in current-state are an empty vector for some reason.


you probably don’t want load-field, since it assumes it already knows the target


targeting supports append on a to-many edge of to-many results

Gleb Posobin22:12:34

But targeting needs the result to be of the form {:user/posts [post1 ... postN]} to be able to insert this somewhere?

Gleb Posobin22:12:16

I know the target on the additional loads where I am using load-field, just capture this in the onclick handler of a button.

Gleb Posobin22:12:45

I don't know the target on the first load, so using load! for it.


I’d prefer to use the following load:

(load! this :user/posts PostComponent {:target (t/append-to [:user/table id :user/posts]
                                       :params {:user/username "bob" :page n})


the posts resolver on the back-end has an input…is that :user/username?

Gleb Posobin22:12:53

No, there are two resolvers: :user/username -> :user/id and :user/id -> :user/posts.


got it, that’s fine. You just need to get a resolver that can work for moving the parameter into the current entity input context for pathom


so, you have some choices. One thing you could do is add a resolver that does that (no input, id output), but I don’t know if that would loop or if pathom gives up on a resolver when it fails to resolve…I think it does, just don’t remember. The other thing you could do is add a pathom plugin that would look for a special param that can be used to set up inputs.


in the latter case you’d just use a ns-qualified parameter for triggering that


:params {:pathom/context {:user/username "bob"}}


something like that


I don’t remember the exact magic incantation for moving a known value into the current resolver context, but I’m sure it’s in the book

Gleb Posobin23:12:38

Hmm, pathom/context seems to work only for idents.

Gleb Posobin00:01:46

But I can rewrite the corresponding function I guess.

Gleb Posobin17:01:18

Ok, I have changed the query-params-to-env-plugin from rad to also save the :pathom/context key into the current pathom entity, and it works with the load! with targeting and passing the :pathom/context in the params.

bananadance 3
Gleb Posobin17:01:04

Weird that this is such a hard to use usecase.


well, ultimately it is just a matter of getting the server to respond to a query. Most of the hard work is actually done for you.


but you do have to understand how to leverage the elements of the tools. No getting around that core understanding