These are both fantastic resources — I hadn't seen Chris’s articles on building a Fulcro app — reading them now, as well as re-reading the Fulcro 3 docs. Thanks, @holyjak . @souenzzo : also super helpful. @holyjak noted earlier today that part of my problem was an illegal EQL statement.
thank you for both this and the troubleshooting articles
is there any substantive difference between fulcro and f-rad if i don't intend to use any forms or the like in my project?
also @tony thank you for those f-rad walkthrough videos on your channel, they are very helpful.
@ajsnow2012 fulcro rad is an add-on library. It is Fulcro
so, if you don’t intend to need/use the data model patterns or pre-written state machines (for forms and report) or just have no forms/reports, then there is no reason to use it at all.
I mostly write business apps that are 95% forms and reports. Daily tasks are: evolve schema, add forms, add reports.
I am trying to modify the signup part of the fulcro-template. There is a signup mutation, and after it is done I want to save the returned session information to the part of the db corresponding to the Session component, or if there is an error I want to display that error in the Signup component, so save the error in the part of the db corresponding to Signup. How do I do such conditional m/returning
?
> invoke that mutation through a transaction (or merge? or merge-component? with an exclamation mark afterwards or not? or load?
I am sorry to say so but I am afraid you need to spend some more time with the book to get more familiar with the concepts first otherwise you are just up to for confusion and suffering 🙂
The signatures of the functions and their docstring answer it. merge-component!
takes the app and mutates it while merge-component
takes the state and is pure. merge!
is a superset of merge-component! as it lets you merge an arbitrary tree. Merge-component is tuned for that particular case of merging a component, as visible from its argument list. From its docstring:
> NOTE: This function assumes you are merging against the root of the tree. See
> merge-component
and merge-component!
for relative merging.
I am exaggerating a bit, of course, but still feeling overly overwhelmed having spent the last five days on figuring out fulcro and still being on the login/signup flow. I've read the docstrings before saying that, it's just when learning it's not clear what to focus on: "state" and "app" didn't register as different things for me I guess.
Or actually a simple join on ident https://blog.wsscode.com/pathom/v2/pathom/2.2.0/connect/resolvers.html#SingleInputs: [{[:component/id Session] [:session/failed?]}] (guessing the names here)
Or should I process the returned result in ok-action, and do (swap! env ...)
? Is there anything better than just updating and associng, some way to merge it more thoughtfully?
I still don't have a mental model around why the requests to the backend have to match the component structure on the frontend tbh.
Ah, right, that won't work because the components related to the current user won't refresh then.
See https://book.fulcrologic.com/#_mutations_and_state_machines under the point options-and-params
Yeah, doing something like that with state machines I have figured out a day ago when I was fixing the login flow to work for my app.
Without the state machines I have just figured out that doing (swap! state merge/merge-component (comp/registry-key->class :app.ui.root/Session) response)
works.
@souenzzo do you want to send a PR with a link to it ☝️? 🙏
Not sure what you mean by backend request matching components. A key concept in F is data model (entities, idents, has-a relations). It is defined through defsc which thus doubles as a piece of UI and an entity definition. Which is what you want 99% of the time. But it's canonical to create defsc only meant for the data model, with no UI role. UI = f(data) so it's most logical for the data to match the shape of the UI (or a subset thereof)
"remote request match component structure" doesn't sound right, I think you should elaborate more to see what's missing in your mental model. Usually remote request is just (load app SomeComponent)
- you just specify the component and don't have to care about its structure. On the other hand, in every component, you have to make sure that initial state
and query
have the same structure.
In ok-action you can submit a new transaction based on the result. But perhaps there is a simpler data model to provide the desired functionality?
just a reminder: the db explore of Fulcro Inspect is quite handy for debugging in cases like this
Right, but it doesn't help me figure out what I do need to use...
I found that there is a merge-component! function, but I can't refer to the component's class because it would make a circular reference. Is there a way to go around this?
Perhaps there is, but I don't understand what would work. I have two different components in my UI: Session and Signup. Signup is the signup form, session is the component that holds the current session data. When signup is successful, I need to update the data for the session component. I am trying to do it with a new mutation (so the ok-action would invoke that mutation through a transaction (or merge? or merge-component? with an exclamation mark afterwards or not? or load? AAAAAA so much stuff and none seems to work)) now.
How I imagine it working in the abstract: the signup endpoint on the backend returns the data with two parts, for the session and for the signup, fulcro gets that data and puts it in the db in the corresponding components.
@posobin it's a bit hard to imagine what you're having. Can you post the code somewhere?
I am modifying the template: https://github.com/fulcrologic/fulcro-template/blob/master/src/main/app/model/session.cljs In the template after you sign up you have to log in. I am trying to get rid of having to log in, so that after sign up you are logged in automatically.
(the signup mutation is at the bottom)
let me see...
Logged in with additional data returned from the server saved to session (say it returns some additional entries that session uses, like session token and user id), not just with the data sent to the server (email in this case).
I need some time to get into details of this template, but I suggest modifying SignUp component to mimick LogIn, which already query for Session
inside of it
and modifying the session uism to handle sign up event, then trigger that event in the body of SignUp ui
that means getting rid of sign-up!
mutation and trigger uism events instead
Hmm, so you mean to have two state machines, one for login and one for signup?
to sum up: what you're trying to do is doable with uism, and in fact has already been implemented as seen in log-in. Focus on the uism, adapt it and avoid low level mutations for now.
And pass both components as actors to the signup state machine?
@posobin it's better to keep them in the same uism - the session uism
Oof that sounds dangerous.
just add sign-up event to the session
But the components are different, and the error messages have to be handled differently?
not that dangerous, it's all about session. Otherwise there'll be duplication regarding logged-in status
I suggest modifying the SignUp to mimick LogIn component
Ok I'll try, thank you.
Though need a way to understand the low level mutation stuff too.
Not complaining, just notifying of a behavior I found a bit puzzling. Suppose the following mutating steps: • State A • Synchronous tx -> State B • [Render] State B • Asynchronous tx -> Restoring State A • [Render] State B instead of A My understanding is that the async tx system caches the state resulting form the last tx cycle and this cached state does not seem to be updated on synchronous tx. So the async tx system is only seeing State A -> State A => no render
Yeah I can tell it's a bit too abstract. No worries, thanks for your efforts. I have encountered quite a few weird behaviors like that but I am in a massive rush currently so don't have much time for repro cases... I'll try later when I slow down :)
Somewhat related, https://hotwire.dev
That sounds like you have a weird nesting issue inside of a swap. Otherwise I would consider it a potential bug
The tx system is supposed to run optimistic actions in some order with atomicity. Async mixed with sync can change the order from submission, but the default system even queues the async ones so order is preserved. If you write a mutation that has an action that does the equivalent of this:
(action [{:keys [state]}]
(swap! state some-fn))
where some-fn
calls a synchronous transact, then you’ll see the state itself go back in time (or lose values), since the outer swap will overwrite what the inner thing did.
If you’re not seeing that, then there is also a debounced render that could be the problem. There is an after-render
option to help with that case. Animation frames use requestAnimationFrame to prevent view computation more than 60x a second (sync can actually override that, since it is trying to make things as fast as possible and an async animation frame delay on top of the computation delay (which could be more than 16ms) can easily push you further under 60fps), but the delayed render should still use the state atom for view computation.
So, are you seeing flicker and a final real state that is correct, or are you “losing changes”. I’ve only ever seen the latter when you nest swap!
Lift anyone? Lift is a Scala library similar to HTML Over The Wire (e.g., see https://demo.liftweb.net/lazy). I used for a medium size project 5-6 years ago and found it a nice approach (the functional closures on the server were really cool), until I needed more js in the front-end for better interactivity, then it was a major pain.
I went and read the RFC for React server components, and there are a few interesting things there. The biggest one “zero bundle size”, which is leveraging the server for components that are “code heavy” (their example is markdown rendering). I’m not sure I agree with the solution (which requires you run node on the back-end, it would seem), since you could just have a markdown server request/response and not write the silly markdown format in client-style code at all. “Full access to the back-end”…well, for that one I guess what they’re really saying is “no need to write an API for the client to access what it needs from the back-end”…that does eliminate some complexity, but only by adding the requirement of running node for your back-end? Pathom resolver seems easy enough. Client-server waterfalls are due to the mistake of loading on mount (which I commonly tell ppl not to do for reasons just like this)…so mixing into a solution like this doesn’t compute well for me. “Automatic code splitting” is a very nice feature. We could add such a thing to Fulcro, I just haven’t had the time to code it. It mainly requires you configure each loadable thing as a loadable module in cljs. Not sure I care about doing that at a component level, but certainly large applications lazy loading sub-systems is appealing (and possible without RSC). My ideal would be a top-level router-integrated solution, which is a pure library concern (other than telling the compiler what to split). That said, I’m a little envious of a completely automatic solution for this (but also wonder what the hidden “catch” might be…we all love the build systems in js-land and how wonderfully transparent they are when they go wrong).
The state in the DB is fine. It is about the rendering. What I noticed is embedded heavily in my app so I won't be able to provide you with a minimal reproducible case but I noticed it with while implementing dropdowns. 1. On user click, Async TX for opening a dropdown. DB update and render OK. 2. Later, on user-click, Sync TX for closing it (must be Sync). DB update and render OK. 3. Later, on user-click, same Async TX as in (1) for opening the same dropdown. DB update OK, but no re-render. So the dropdown was open in the DB, but stayed closed visually in the DOM. I solved it by never reusing the same ident so that even when opening the same dropdown, a new ident is generated everytime, so that on (3), the state is not restored to (1). Does it make sense? (I feel it does somewhere) I never call any kind of transaction within a transaction, it feels like poor composability (personal opinion).
Okay, not sure why at all but it is definitely about idents. The Sync closing TX operates on a parent with a well defined ident in any case. If it leads to unmounting a child dropdown which does not have an ident, the exact same problem happen.
Using the default multi-root renderer.
@tony.kay https://fulcro.fulcrologic.com/ "Is Fulcro For Me?" still reads "Clone and edit the https://github.com/fulcrologic/fulcro-template" - perhaps change that to RAD?
torn…not going to change it for now. If you know Fulcro and your starting a real project with real goals, I’d use RAD. If you’re trying to learn Fulcro from nothing, I’d use the template (or possibly even less).
render refresh requires props to change (default shouldComponentUpdate compares prior to current props)…is the “open” flag correctly queried for and changing?
new idents every time sounds like real overkill
it really sounds like something isn’t connected right to me. The sync/async thing should not matter
The parent has one prop designating the ident of its child dropdown, thus this props changes everytime when:
Async OPEN 1st -> Sync CLOSE -> Async OPEN 2nd
But between Async OPEN 1st
and Async OPEN 2nd
, it wouldn't change if I didn't forcefully created a new ident for that dropdown. That why I though that async TXs were kind of ignoring completely what is happening on a synchronous level.
a parent’s prop never designates an ident. The normalization MIGHT represent it that way in DB, but at render what you have is a map. That map must have stuff in it that changes, or no render refresh.
if you change the ident, then you get a key change (most likely) which causes a react unmount/remount
which will always delete and re-add nodes to dom, which will of course make a visual impact 🙂
I meant, its not about a boolean flag. In order to avoid querying stuff I might not need (because the dropdown is closed), I work with a join. The opening TX creates the dropdown in the DB and assoc it on the parent. The parent joins it in its query. Similarly, the closing TX removes the dropdown from the DB and naturally dissoc it from the parent.
The point is, the parent changes everytime a child dropdown is closed or open, always, I can see it in the DB.
Thanks a lot! I will correct my advice to beginner accordingly then.
🙏 Any more resources to add to https://github.com/holyjak/awesome-fulcro/ ?
so, if you can make a repro case I could take a look…this is too much hand-waving for my taste
Purely FYI: came up in my rss reader. https://www.reddit.com/r/Clojure/comments/kibrfs/fulcro_as_fullstack_framework_or_else/
Hey @holyjak I’ve found this older OMNext / Eql tutorial useful for understanding query syntax: https://awkay.github.io/om-tutorial/#!/om_tutorial.D_Queries
Thanks!
could add this project template: https://github.com/dvingo/dv.fulcro-template and this project is an example client-only fulcro app hitting a rest api: https://github.com/dvingo/pathom-client-wikipedia