Fork me on GitHub
#fulcro
<
2022-09-26
>
Eric Dvorsak13:09:50

@wilkerlucio did you manage to set up deep-linking with fulcro?

wilkerlucio13:09:01

deep-linking?

Eric Dvorsak14:09:38

Opening the spa at a specific route from the url

Eric Dvorsak14:09:19

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

wilkerlucio14:09:53

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?

Eric Dvorsak14:09:37

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

Eric Dvorsak14:09:47

it's working so I'm just leaving it here for future onlookers

Eric Dvorsak09:09:09

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

❤️ 1
Jakub Holý (HolyJak)18:09:24

RAD's html history support might be useful here

zhuxun215:09:42

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?

tony.kay03:09:13

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

zhuxun215:09:53

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.

zhuxun215:09:50

Instead, could we say something in Comment or Blog that locally "breaks" the recursion of query composition?

zhuxun215:09:43

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?

zhuxun216:09:44

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

cjmurphy19:09:08

You would create a third defsc that has the expanded query and no rendering code.

👍 1
tony.kay03:09:56

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.

tony.kay03:09:08

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.

zhuxun202:09:29

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

zhuxun202:09:24

@U0CKQ19AQ Or are you suggesting two consecutive (df/load!) calls, first with LoginTray and second with ProjectsView?

👆 1
zhuxun202:09:24

But that would be two server round-trips right?

tony.kay03:09:22

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.

tony.kay04:09:00

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.

tony.kay04:09:52

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.

tony.kay04:09:02

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

tony.kay04:09:39

If you’d have interest in testing it use git SHA b0b59f5c279adefee8e894c2339cce2c2be2436c

❤️ 1
tony.kay04:09:53

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

tony.kay04:09:33

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.

tony.kay04:09:34

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

tony.kay04:09:58

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

zhuxun213:09:53

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

zhuxun213:09:19

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.

zhuxun213:09:00

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 []})?

tony.kay14:09:28

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.

tony.kay14:09:27

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.

tony.kay14:09:02

saying that you want it all at once is an optimization that you’ve been told how to do

zhuxun215:09:46

@U0CKQ19AQ I see your point now. Thanks!

tony.kay15:09:53

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

zhuxun215:09:21

Yeah I will give it a try!

zhuxun216:09:59

It does not seem to work at all... I changed my deps.edn:

zhuxun216:09:26

Changed my application construction:

zhuxun216:09:04

Duplicated my load several times:

zhuxun216:09:52

And still seeing multiple api calls:

tony.kay17:09:01

1. Did you change your server? 2. I think batched-reads? with a ? What does the docstring say?

tony.kay17:09:50

* `: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)."

tony.kay17:09:34

(If you’re using c.f.f.s.api-middleware/handle-api-request, then your server will work)

tony.kay18:09:16

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

nivekuil21:09:28

does the batched-reads stuff work with synchronous transactions?

tony.kay22:09:19

Not if you changed the algorithm

tony.kay22:09:34

You mean you installed sync tx processing?

nivekuil23:09:33

yeah that's why I don't use batched-reads

zhuxun203:09:35

@U0CKQ19AQ Yeah, after 1. and 2. the batched reads seems to be working