fulcro

Book: http://book.fulcrologic.com, Community Resources: https://fulcro-community.github.io/, RAD book at http://book.fulcrologic.com/RAD.html
genekim 2020-12-23T01:06:45.266100Z

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.

Mr. Savy 2020-12-23T02:04:54.266400Z

thank you for both this and the troubleshooting articles

❤️ 1
Mr. Savy 2020-12-23T02:27:01.267600Z

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?

Mr. Savy 2020-12-23T03:59:40.268900Z

also @tony thank you for those f-rad walkthrough videos on your channel, they are very helpful.

tony.kay 2020-12-23T04:21:57.269200Z

@ajsnow2012 fulcro rad is an add-on library. It is Fulcro

tony.kay 2020-12-23T04:22:29.269900Z

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.

tony.kay 2020-12-23T04:23:58.271100Z

I mostly write business apps that are 95% forms and reports. Daily tasks are: evolve schema, add forms, add reports.

Gleb Posobin 2020-12-23T04:48:13.274400Z

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?

Jakub Holý 2020-12-23T08:51:02.284300Z

> 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.

Gleb Posobin 2020-12-23T09:02:13.285Z

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.

Jakub Holý 2020-12-23T09:14:06.285600Z

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)

Gleb Posobin 2020-12-23T05:08:47.276100Z

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?

Gleb Posobin 2020-12-23T05:10:44.276400Z

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.

Gleb Posobin 2020-12-23T05:34:38.277100Z

Ah, right, that won't work because the components related to the current user won't refresh then.

👍 1
Jakub Holý 2020-12-23T08:46:11.284100Z

See https://book.fulcrologic.com/#_mutations_and_state_machines under the point options-and-params

Gleb Posobin 2020-12-23T08:51:33.284600Z

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.

Gleb Posobin 2020-12-23T08:53:38.284800Z

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.

👍 1
Jakub Holý 2020-12-23T06:34:00.277500Z

@souenzzo do you want to send a PR with a link to it ☝️? 🙏

✔️ 1
Jakub Holý 2020-12-23T06:41:19.277700Z

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)

2020-12-23T06:48:56.277900Z

"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.

Jakub Holý 2020-12-23T06:50:25.278200Z

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?

2020-12-23T06:51:14.278400Z

just a reminder: the db explore of Fulcro Inspect is quite handy for debugging in cases like this

Gleb Posobin 2020-12-23T06:56:58.278600Z

Right, but it doesn't help me figure out what I do need to use...

Gleb Posobin 2020-12-23T06:59:21.278800Z

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?

Gleb Posobin 2020-12-23T07:03:37.279Z

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.

Gleb Posobin 2020-12-23T07:07:22.279200Z

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.

2020-12-23T07:15:55.280200Z

@posobin it's a bit hard to imagine what you're having. Can you post the code somewhere?

Gleb Posobin 2020-12-23T07:22:27.280300Z

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.

Gleb Posobin 2020-12-23T07:22:37.280600Z

(the signup mutation is at the bottom)

2020-12-23T07:24:19.280800Z

let me see...

Gleb Posobin 2020-12-23T07:28:09.281Z

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).

2020-12-23T07:37:20.281200Z

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

2020-12-23T07:39:10.281400Z

and modifying the session uism to handle sign up event, then trigger that event in the body of SignUp ui

2020-12-23T07:40:50.281600Z

that means getting rid of sign-up! mutation and trigger uism events instead

Gleb Posobin 2020-12-23T07:44:36.281800Z

Hmm, so you mean to have two state machines, one for login and one for signup?

2020-12-23T07:44:45.282Z

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.

Gleb Posobin 2020-12-23T07:44:57.282200Z

And pass both components as actors to the signup state machine?

2020-12-23T07:45:36.282400Z

@posobin it's better to keep them in the same uism - the session uism

Gleb Posobin 2020-12-23T07:45:53.282600Z

Oof that sounds dangerous.

2020-12-23T07:46:08.282800Z

just add sign-up event to the session

Gleb Posobin 2020-12-23T07:46:46.283Z

But the components are different, and the error messages have to be handled differently?

2020-12-23T07:46:48.283200Z

not that dangerous, it's all about session. Otherwise there'll be duplication regarding logged-in status

2020-12-23T07:47:27.283400Z

I suggest modifying the SignUp to mimick LogIn component

Gleb Posobin 2020-12-23T07:48:24.283600Z

Ok I'll try, thank you.

Gleb Posobin 2020-12-23T07:49:02.283800Z

Though need a way to understand the low level mutation stuff too.

Helins 2020-12-23T12:23:07.290300Z

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

Helins 2020-12-24T14:55:18.311700Z

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 :)

dgb23 2020-12-23T13:41:04.291100Z

tvaughan 2020-12-23T13:57:56.291200Z

Somewhat related, https://hotwire.dev

tony.kay 2020-12-23T15:57:30.291800Z

That sounds like you have a weird nesting issue inside of a swap. Otherwise I would consider it a potential bug

tony.kay 2020-12-23T16:28:32.292300Z

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!

tony.kay 2020-12-23T16:50:51.292900Z

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.

tony.kay 2020-12-23T17:02:33.293300Z

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).

👍 2
Helins 2020-12-23T17:28:14.293600Z

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).

Helins 2020-12-23T18:11:53.294Z

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.

Helins 2020-12-23T18:12:33.294200Z

Using the default multi-root renderer.

Jakub Holý 2020-12-23T18:22:04.295Z

@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?

tony.kay 2020-12-23T18:29:45.296300Z

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).

👍 2
tony.kay 2020-12-23T18:32:22.296500Z

render refresh requires props to change (default shouldComponentUpdate compares prior to current props)…is the “open” flag correctly queried for and changing?

tony.kay 2020-12-23T18:34:06.296700Z

new idents every time sounds like real overkill

tony.kay 2020-12-23T18:35:03.296900Z

it really sounds like something isn’t connected right to me. The sync/async thing should not matter

Helins 2020-12-23T18:45:07.297100Z

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.

tony.kay 2020-12-23T18:46:26.297300Z

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.

tony.kay 2020-12-23T18:46:55.297500Z

if you change the ident, then you get a key change (most likely) which causes a react unmount/remount

tony.kay 2020-12-23T18:47:17.297700Z

which will always delete and re-add nodes to dom, which will of course make a visual impact 🙂

Helins 2020-12-23T18:53:27.298300Z

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.

Helins 2020-12-23T18:55:05.298500Z

The point is, the parent changes everytime a child dropdown is closed or open, always, I can see it in the DB.

Jakub Holý 2020-12-23T19:30:04.299Z

Thanks a lot! I will correct my advice to beginner accordingly then.

Jakub Holý 2020-12-23T19:30:48.299600Z

🙏 Any more resources to add to https://github.com/holyjak/awesome-fulcro/ ?

tony.kay 2020-12-23T19:32:48.299700Z

so, if you can make a repro case I could take a look…this is too much hand-waving for my taste

genekim 2020-12-23T19:57:16.300500Z

Purely FYI: came up in my rss reader. https://www.reddit.com/r/Clojure/comments/kibrfs/fulcro_as_fullstack_framework_or_else/

👀 4
2020-12-23T20:06:39.300700Z

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

Jakub Holý 2020-12-23T21:01:47.301Z

Thanks!

2020-12-23T21:28:36.301200Z

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

👍 2