Fork me on GitHub
Mr. Savy03:12:49

I make some component X with some param represented as Y. In X's body I have a ui factory that I pass Y into so I can build the data. In general, I would suspect that if Y is null when it's not supposed to be, the cause would be either that its parent isn't calling it right, or the query is incorrect.

Mr. Savy03:12:21

if it's not that the function is being called wrong, and if it happens when loading in data, I would then want to test it against static data.


I suspect you’re mental model of Fulcro isn’t complete. A lot of people think Fulcro does more “Magic” for you than it does. The steps are literally: On mount: a. It pulls your initial-state tree (which is a literal representation of your view tree data). b. Uses your query, which has the exact same shape as initial-state, to normalize that tree (there are a few more steps, but that’s basically it). c. It then denormalizes that tree via the same query…it literally uses your query, from root to get the props tree. Nearly the exact opposite of (b). d. It calls render and passes that tree of data to your root component. That’s it. On each transaction there is a lot of interesting full-stack stuff going on in terms of making your life more sane (serial processing by default, writes prioritized over reads etc etc), BUT when a transaction has modified the database, Fulcro: a. Gets your root query (which can be dynamic, but usually isn’t) b. Uses that query + db to get UI data tree c. Passes it to root render. Which is very similar to what it does on mount. That is again, it. Rinse, repeat. If you use an alt renderer like ident-optimized or multi-root, then there are some extra features. So you see the model is very very simple: Db change -> denormalize using Root query + db -> Render root -> Loop Don’t confuse yourself thinking there’s this magic part of the query or this magic part of the subtree rendering, or that all sorts of weird side-magic is going on, etc. The mental model is: There is a UI tree, there is a data tree. They must match. The render is a visual reification of the data tree using your render. The query engine in 99% of apps is doing nothing more than following and idents (the main complications are recursion, dynamic queries, wildcards, and unions, which many apps do not even use, and are really nothing very complicated). To get Fulcro “right”, 90% of the time, is to get the shape of your data graph to match the shape of the query (which initial state makes trivial), and then keep it that way (which is up to your application’s logic). load is exactly the initial state+query mechanism, on some sub-graph. You’re making a new part of the tree match some UI that you are about to show.

❤️ 2
Mr. Savy19:12:24

yeah I've struggled at grasping the data pattern for some time, at least when it comes to troubleshooting. stuff like this helps though, so thanks for taking the time to write it! I wish there was an faq page or something with explanations like these. I suspect there's a lot problem/solution/insight topics that get lost in the archives here which might help connect some of the dots.


The chapter I just revived in the book (3) might help. But I agree, some more docs never hurt

👍 1

@U012MJU8XNU I'm (slowly) grabbing them and putting them on this doc.

👍 1
Gleb Posobin19:12:02

Is there a way to make a catch-all route in the dynamic router? Say I want to have routes /main /settings and /:username, not /user/:username.


I suspect that might be a problem.


I'd look into the function doing the target selection based on the route

Gleb Posobin19:12:25

I have tried adding the catch-all in the end of the defrouter's routes vector, this didn't work (all routes were going to the catch-all then), but it works if I add it at the front.

Gleb Posobin19:12:16

So just add the catch-all as the first route in the router, seems like it tries routes from the end.


The first one is the default, used I suppose when nothing else matches. Surest is to read the code.

Gleb Posobin19:12:02

And how do you handle parameters in URL, like /:username?all-posts=true ?


Look at the docstring of the route fn, it takes extra params. Or/and look at how RAD reports use it

Gleb Posobin20:12:57

You mean dr/change-route!?

Gleb Posobin20:12:22

(it is in the docstring there and solves the problem, thank you, just making sure)


DR has no concept of URLs. Query parameters can be augmented, and can be placed in vector of strings. DR is not tied to the web. It works for native, electron, or whatever other target you might want. HTML5 “interpretation” is up to you

👍 1

When you change routes you can put anything in the route params to pass through

Gleb Posobin03:12:25

I didn't know about the route params, and haven't seen the route params mentioned in the book (and can only find them now in the legacy routers section), that's why I asked.


No problem 😊


Not even in doc strings?

Gleb Posobin07:12:23

It is in the doc string, but didn't think to look there.


The curse of too much documentation. I could easily add 100+ pages to the book covering these kinds of little details that are in docstrings.

👍 1
Gleb Posobin20:12:56

Is the only way to get the route params of the current route in a component is to receive them in the :will-enter function, and return a deferred mutation/load with those params? I am trying to understand how the person/id prop gets passed into the person component in the router example in the book: Is it because the :person/id key is both in the query and the route-segment, it gets exctracted form the route segment and put into the props?


I believe the dr/target-ready mutation sets its ident.


And dr/route-deferred, too, I guess.

Gleb Posobin03:12:51

Yeah, I don't understand it...

Gleb Posobin03:12:50

What if I don't have an ident? Like, I have the .../:username route, but the users are indexed by their ids and not usernames.


You pass a vector of strings to the route. It uses your route segments to turn that into the proper path parameters. This is all separate from idents and queries. It is pure data processing by the router with the intention of composition (of routers) and abstraction (not being tied to the idea of how routes are remembered (e.g. URL), navigated (back button), etc). Don’t conflate them. Dynamic routers are pure UI routers whose sole purpose is to get the right thing on the screen. For your convenience, the design is meant to be easy to use from a URL perspective (splitting a URL on / gives you the vector). The parameters to will-enter are purely from the vector and parameters you pass to change-route, and have nothing to do with the state db or query.

Gleb Posobin05:12:20

Hmm, I see. But how do I get the result of the router match into the component? Say the route-segment is [:username] (or ["user" :username]), how do I get the username into the component? Is issuing a load/mutation in route-deferred the only way? There will be a delay then iiuc.


Make a mutation that puts it there and then changes the ui route.

Gleb Posobin06:12:31

What about the first page load? I am not using SSR.

Gleb Posobin06:12:14

I can live with the delay between when the component is rendered and when it learns its route params, just curious.

Gleb Posobin06:12:01

Hmm, though not sure why the component gets rendered before the path param is loaded if I call dr/target-ready in the mutation after the swap! on state that adds the param.


so, the ideal pattern for a production-ready first page load, IMO, is to have the html render something that shows a loader. The app loads with a flag that is false in initial state (:ui/ready? for example). The rendering when that flag is false is exactly what is already in the app div the HTML generated. So, the user doesn’t really see the transition from static HTML to running app. You do your initialization, route, and then some aspect of your final initialization (which might be route-deferred) changes the flag to true. Now your app renders, and you’re right where you want to be. You are making a frame-by-frame animation via state. Fulcro does not know what you want to show when. It just renders each frame as it happens. You’re ultimately in control of what renders. Dynamic routing has a central set of concerns: composable UI switches that let you have some control over what happens as things come and go from the screen. Nothing’s perfect, and there are certainly imperfections in D.R. I initially wrote that because ppl were complaining they didn’t know how to go about making a ui router, and so I built that as an example, but it was good enough that I released it. It’s since gone through some revision, but it still isn’t ideal in every way. Fulcro’s rendering, as I’ve said at least 3x this week alone, is very very naive: mutation -> render -> mutation -> render. Component’s don’t have much control over when they render, only what. The state machines behind D.R. essentially run mutations to move from state to state, meaning that some (unintentional) frames of state might be rendered…this is where some refinement in the internals would be nice. That said, my expectation is that if you return a route-deferred then that route will not be visible until you say target-ready. The route params are passed in before you can even say route-deferred, so I honestly don’t know what you’re talking about, unless I’m misunderstanding you’re description, or your code doesn’t match your description.


Stepping through state transitions with fulcro inspect can be a good way to gain insight into what is happening in your app.


After quite an extended break, I published another post in my gift list series detailing how to deploy a fulcro app using dokku: Always happy to hear any feedback.

👍 5

Yeah sure

❤️ 1