This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-09-26
Channels
- # announcements (1)
- # babashka (106)
- # beginners (11)
- # biff (7)
- # calva (16)
- # clj-kondo (40)
- # clj-on-windows (5)
- # clj-yaml (10)
- # clojars (4)
- # clojure (37)
- # clojure-austin (22)
- # clojure-australia (1)
- # clojure-europe (40)
- # clojure-nl (1)
- # clojure-norway (10)
- # clojure-spec (6)
- # clojure-uk (6)
- # clojurescript (13)
- # conjure (11)
- # cursive (14)
- # datalevin (8)
- # datascript (5)
- # emacs (39)
- # events (1)
- # fulcro (55)
- # gratitude (4)
- # holy-lambda (2)
- # humbleui (9)
- # instaparse (1)
- # lsp (3)
- # malli (12)
- # meander (2)
- # membrane (7)
- # nbb (1)
- # off-topic (16)
- # pathom (9)
- # releases (3)
- # sci (14)
- # shadow-cljs (25)
@wilkerlucio did you manage to set up deep-linking with fulcro?
deep-linking?
Opening the spa at a specific route from the url
I found a Convo in Google search, I managed to get something working based on some snippets you posted but was wondering if you came up with something more solid
Im guessing I'm missing what you are looking for here, how to make a link for an instance at a SPA? to be honest its been a while since I did any fulcro code really, but this seems to be regular routing, is there anything special about the case you have in mind?
This is the convo I was refering to https://clojurians-log.clojureverse.org/fulcro/2020-01-29
I have a routing na,mespace with this snippet
(defn url->path
"Given a url of the form \"/gift/123/edit?code=abcdef\", returns a
path vector of the form [\"gift\" \"123\" \"edit\"]. Assumes the url
starts with a forward slash. An empty url yields the path [\"home\"]
instead of []."
[url]
(-> url (str/split "?") first (str/split "/") rest vec))
(defn path->url
"Given a path vector of the form [\"gift\" \"123\" \"edit\"],
returns a url of the form \"/gift/123/edit\"."
[path]
(str/join (interleave (repeat "/") path)))
(defonce page-history
(doto (History.)
(.setEnabled true)))
(defn listen-nav-change [f]
(gevents/listen page-history "navigate" #(f % (.-token ^goog.history.Event %))))
(defn change-route! [path]
(.setToken page-history (path->url path)))
(defn change-route-from-nav-event [app]
(fn [_ token]
(dr/change-route app (url->path token))))
(defn init! []
(listen-nav-change (change-route-from-nav-event SPA))
(dr/change-route SPA (url->path (.getToken page-history))))
and added this to the init of the spa
(routing/init!)
it's working so I'm just leaving it here for future onlookers
@wilkerlucio btw I'm back on using pathom it was a real pleasure working with it a couple years ago, and I love what you are doing with pathom3, keep up the good work you are awesome 👍
RAD's html history support might be useful here
It seems that currently incremental loading is achieved through pruning the query when calling df/load!
, which usually occurs at near-root level of the UI. Is there a way to put the responsibility of cutting off a branch at the near-tip level of the UI?
Of course you can! Just make your own load function that scans the query for metadata, and have that rewrite the args to df/load!
to include those in :without
For example, if my UI has Session > Account > Person > Blog > Comment
, and the page is loaded by saying (df/load! this ::current-session Session {...})
. Right now Session
has to know the existence of Comment
in order for it to prune the :blog/comment
field.
Instead, could we say something in Comment
or Blog
that locally "breaks" the recursion of query composition?
What I mean is something like:
(defsc Blog [_ _]
{:query [:blog/id :blog/body ^loading-break-point {:blog/comments []}]
...}
...)
So that when you do (df/load! ... Session {:respect-break-points? true})
in a near-root level, the :blog/comments
will be pruned?Unrelated question: If I'm loading a screen where the UI needs two overlapping trees, is it possible (and is it idiomatic) to deep-merge them? For example, if on the screen there's the *login tray, and a projects view*. The login tray's query is
(defsc LoginTray [_ _]
{:ident :account/id
:query {:account/id :account/name}})
But the projects view's query is
(defsc Project [_ _]
{:ident :project/id
:query [:project/id :project/name])
(defsc ProjectsView [_ _]
{:ident :account/id
:query [:account/id {:account/projects (comp/get-query Project)}])
Now, if I want to make a single query that loads both LoginTray
and SessionView
, that is, to query
[:account/id :account/name {:account/projects [:project/id :project/name]}]
Can I easily combine the two queries with built-in functions in Fulcro?You would create a third defsc that has the expanded query and no rendering code.
The design is such that such overlapping queries can happen and will work perfectly fine (though may do some slight additional traffic). The composition does not break. Fulcro’s internals will only overwrite values that are requested, so independent of load order those two will work perfectly, and you need not worry about making a third thing just to load.
In general black box composition is the goal. The primary exception is that parents must know about their children, since they compose them in, but that is standard in any compositional system.
@U0CKQ19AQ I'm afraid I'm still not clear. What do you mean by "overlapping queries"? And what do you mean by "composition"? The query
[:account/id :account/name {:account/projects [:project/id :project/name]}]
needs to be somehow composed from LoginTray
and ProjectsView
, but I do not know how. Are you saying I should manually write that query out? (And even that I won't be able to fill in the neccessary metadata).@U0CKQ19AQ Or are you suggesting two consecutive (df/load!)
calls, first with LoginTray
and second with ProjectsView
?
stop micro-optimizing. Fulcro’s internals can try to combine compatible loads that are queued while you hold the UI thread, and there is experimental opt-in support for it to do more aggressive network optimizations (requires server and client configuration). load!
is meant to be a black-box composable call. So yes, calling it for the two things that are not supposed to be coupled is not only supported but encouraged. If you get into a situation where the round trips are actually a measured problem, then you can experiment with configuring the network combining processing.
I might have a customer account that has 100 different facts provided by 100 different resolvers, and have 4 different components on screen at the same time that are all showing orthogonal things. Yes, I would issue 4 loads. And if there was a real performance problem with that, then yes, I might consider taking a route where I combine the queries into a single one an issue a single load, but I’d be giving up compositional independence for a performance optimization, which would then be potentially hard to reason about and refactor later. “I can’t issue more than one load” is an optimized endpoint, not a starting point. This case is so rare that I don’t think I’ve ever measured a real problem that needs the optimization.
The read combining is something I’d consider for complex scenarios. Again, I’ve not hit them a lot in practice, which is why the read combining isn’t a well-tested default implementation in Fulcro, but is instead a configurable option.
ah, actually, the advanced read combining has never been merged 😛 it is still on a branch. The batched-networking
branch. I should really move those changes to an opt-in algorithm and release it so people can try it and give feedback. If memory serves correctly it was working fine, but since it is in such an important code path I didn’t feel safe releasing it as-is (in the core tx processing).
If you’d have interest in testing it use git SHA b0b59f5c279adefee8e894c2339cce2c2be2436c
It adds a new option to the application constructor: batched-reads?
. If you set that to true
, and make your server compatible with it, then it’ll do the combining and make one round trip
Normally the EQL on-the-wire protocol sends a vector (the tx) and returns a map. With read combining the client sends a map {:queries [ [q1] [q2] … }]
and the server has to detect that it got a batch, and then run each of those and return a vector of maps that is the response to each of those, in the same order they appeared. This is trivial to write, since it’s just (mapv (fn [q] (parser env q)) (:queries request))
. You still have to support getting a vector and returning a single map.
I may have also been delaying a release of that because I was considering batching mutations in as well, but that’s harder to handle correctly on the server (because the mutations will be writes, and then queries will be reads, and you may have transaction semantics that are hard to reason about).
but as it stands I think it’s a good optimization for your specific use-case, because it is always composing the reads into a single network request that would be nice, and Fulcro is in a relatively rare spot where it can actually do that for black-box composition (e.g. those 4 components all issue their own separate loads without knowledge of each other, but only one network request happens) and have that be pretty immune to breakage during refactoring
@U0CKQ19AQ For my use case, it is not the speed of the round trips that I am worrying about but rather the uncomfortable feeling that between the two (df/load!)
calls the client db is in an inconsistent state.
Because so much of my client-side UI logic is dependent on the fact that the client side DB is as consistent as it can be.
Also, between the two loads, there's a screen refresh which is visually very unprofessional. I guess I might want to stop the react rendering between these two loads.
Is there an easy way to do that? Am I supposed to do (df/load! ... {:refresh []})
?
IMO rendering is the job of the component. it should render correctly no matter what data it has, since it may not yet have any. There is no render disabling between loads. If you really think you have to have a single round trip then the prior answers are the solution.
My banking website has 4 widgets that pop up with spinners when I log in…saying that you have to have it all or it looks unprofessional is patently false.
saying that you want it all at once is an optimization that you’ve been told how to do
@U0CKQ19AQ I see your point now. Thanks!
sure, if you try out that combined read support let me know how it goes. I need to get back to that, because it is a nice feature
1. Did you change your server?
2. I think batched-reads?
with a ?
What does the docstring say?
* `:batched-reads?` - Default false. When true, the tx processing will attempt to batch together multiple loads into
a single request. WARNING: The server MUST support this as well (the latest built-in handle-api-request does)."
(If you’re using c.f.f.s.api-middleware/handle-api-request, then your server will work)
I also just rebased that branch to the lasted Fulcro version, so use SHA 74d9fb28f91294e15f4b76978205f7e0027f55cd instead, since that will have all of the latest changes to Fulcro itself
@U0CKQ19AQ Yeah, after 1. and 2. the batched reads seems to be working