This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-11-28
Channels
- # announcements (1)
- # aws (1)
- # babashka (41)
- # beginners (21)
- # biff (7)
- # calva (102)
- # cider (8)
- # cljs-dev (1)
- # clojure (8)
- # clojure-bay-area (2)
- # clojure-dev (30)
- # clojure-europe (40)
- # clojure-norway (52)
- # clojure-sweden (9)
- # clojure-uk (5)
- # clojurescript (15)
- # cursive (7)
- # data-science (1)
- # datomic (23)
- # events (1)
- # fulcro (9)
- # humbleui (23)
- # hyperfiddle (46)
- # introduce-yourself (1)
- # jackdaw (2)
- # jobs (2)
- # london-clojurians (1)
- # malli (13)
- # off-topic (8)
- # re-frame (36)
- # remote-jobs (1)
- # shadow-cljs (4)
- # specter (4)
- # squint (1)
- # transit (4)
- # vim (1)
If I have an atom with a sequence of data server-side and watching it from the client. What happens when I add a value. Is the whole sequence of data sent or the diff?
You can’t watch it from the client, so I presume the e/watch
is server-side, and you’re reading the resulting value client-side.
In this scenario, you’ll get the whole sequence every time it changes (modulo Electric relieving some of it).
If it goes into for-by
server-side before doing something client-side in the closure of it, then you’ll get diffing.
Yep your clarification is correct. Thanks that makes sense with the for-by. So if I did (e/server (e/for-by …)) on a sequence on the backend. Then when I add data to that sequence and remove data from it on the backend. The frontend will only ever receive the diff adding and removing to the rendered output? If so this may radically simplify the design I was considering.
Though I am not rendering to dom and do need the full sequence to pass to GPU at present so maybe it doesn’t quite do it.
You for-by identity-fn
, which can be as simple as a keyword, if your sequence contains maps, for example. Electric will tear down and build up branches based on that identity function.
For example, in this case the branch for 2
will be torn down (which means a small amount of communication with the client), and the branch for 4
will be mounted (which means an amount of data transferred depending on your e/server
and e/client
blocks inside SomeFn
)
(for-by :id [m [{:id 1} {:id 2} {:id 3}]]
(SomeFn. m))
;; =>
(for-by :id [m [{:id 1} {:id 3} {:id 4}]]
(SomeFn. m))
Perhaps this is clearer:
(e/server
(e/for-by :id [m [{:id 1} {:id 2} {:id 3}]]
(let [id (get m :id)]
(e/client
(dom/div (dom/text id))))))
;; Initial payload to client: 1, 2, 3
(e/server
(e/for-by :id [m [{:id 1} {:id 3} {:id 4}]]
(let [id (get m :id)]
(e/client
(dom/div (dom/text id))))))
;; On update: <teardown instructions for 2>, 4
I.e., only the value of :id
passes the client/server border, so only that is transferred. No sequence, and no map.
Oh that is informative. I suspected that the whole map was transferred but it was just checking on that particular value. I would definitely need the entire map.
Then you just pass m
across the border, provided it contains only serializable values.
I.e., if your map has 1000 key/value pairs, and one changes, you will be transferring 1000 key/value pairs. As opposed to when destructured on the backend. This is an extreme example, it won’t be a problem most likely. Just highlighting the logic of what a unit of change is to Electric in this case.
My current thinking is I keep a count of the sequence and perhaps a hash. Then I could use e/for-by and accumulate it into a set on the frontend and do a count/hash check before passing all the accumulated data into rendering. But I’m unsure if this is the best way to go instead of just doing a more manual diffing process. Like batching the fetches and removing data I don’t want in the sequence anymore on the frontend
My maps are small. But the sequence is potentially large.
My advice would be to not worry about the payload size initially. Go with as plain logic, and basic Electric code as you can. Trim it later when it actually works, and the logic has solidified by moving client/server borders.
I haven’t worked on a solution yet as I was planning to use tmdjs but it was causing fatal errors with missionary
This is what I have at the moment. It's sending the entire sequence every change on demand with no buffering of data. So I would like to optimise this.
realise I could have shared this earlier as some grounding to the context. But yea I will go with for-by
and keep it simple.
I also was not so worried about payload size and more thinking about network request reduction. Like sending and receiving every data point might be silly rather than getting the next chunk at a certain point along the scrolling and just batching the request so plenty of data is there ahead of time.
I believe leo did some work on this here but it hasn’t been integrated into electric yet https://github.com/hyperfiddle/electric/blob/46f9a0dc39f10afcb1fb80d6ff58c50f2ea04055/src/hyperfiddle/incseq.cljc
interesting
I’ve adapted a good 75% chunk of our app to ic-squashed
(the remaining 25% will be the tricky bit).
Again, not going to production on this. But my plan is to 1) track the branch loosely to find problems early, and 2) ic
has stricter requirements on the code that are mostly OK to do in the current version, so get most of the changes required back into our main and make it forwards-compatible. Preparation.
Some observations so far:
1. Wow, amazing effort. 25-45 second compile times drop to ~1 second. There’s awesomesauce in here.
2. I noticed a couple of cases where I can’t reference macros like [hello.world :refer [cat-dog]]
, but had to change them to [hello.world :as hello-world] … (hello-world/cat-dog)
.
3. A case of dom/props
being unable to take a var on :style
. However, destructuring {:keys [position left top]}
and recreating the map {:position position …}
on the style key works.
4. A few cases of things working OK on reload, but not on reboot of the app, and vice versa. Sorry for the vague language, but it’s hard to reproduce an exact case. Just that there could be glitches in the reload logic.
5. CLJS requires sometimes struggle in e/client
blocks. Occasionally I’ve had to wrap them like:
(defn hello-world
[]
#?(:cljs
Oh, and the stricter requirements (and improved warnings) of IC has revealed a handful of things in our code that were just plain wrong, but kind of slipped through the cracks in our current code. Really nice improvements on communicating problems to the developer.
What is ic-squashed
?
I have not yet deeply reviewed the IC changelog, one thing I note as missing is that reader conditional behavior i.e. #?(:clj ... :cljs ...)
is completely changed
i think it now does "the expected, least surprising" thing, which is to say things like (e/client #?(:cljs ...))
can now properly guard cljs-only stuff
in Electric v2 reader conditonals would evaluate on JVM always i think, making them basically unusable
I might have some details wrong, I am not really paying close attention to this workstream yet
Ah, gotcha. I assumed that “hiding” branch like that would lead to sync problems between client and server.
another example: (e/client #?(:cljs #js {"a" 1}))
will do the obvious right thing
I am not completely sure about the "hiding" issue under IC, but hiding a client-only literal i think is harmless and works
One place I had to do this was in the uix
macro we use to mount React components (it would complain that uix.dom
is an unknown class). It probably would have worked OK if I wrapped the two branches locally.
I ended up proxying the functions and made them no-op server-side.
Are you pulling the branch regularly? There was e.g. a fix on requires yesterday. If you provide repros of the issues I might take a look at them. Order of preference is • rcf test • minimal repro in starter app with complete reproduction steps
1. you have incredible patience to tolerate 45 sec compile times, I moved to ic after like 12 2. what's the exact error when this happens? 3. does dom/style work? 4. just checking, did you add the shadow build hook? 5. I think this is actually a feature
1. It’ll be great for sure, that’s a big reason why I want to be as prepared as possible. At the same time, it’s WIP and might change at any moment, so sticking with the current version is better. I’d be prepared to say that even with 45 second compile times and occasionally surprising behaviour, Electric is still better than the alternative.
2. There’s no error, it just silently doesn’t work.
3. Actually, I looked this up, I see that it’s the same problem that was fixed in main. It’ll be fixed when ic-squashed
is rebased on master.
4. Yup, it’s been added.
5. I guess it depends. Simple values are easy to wrap in conditionals. More complicated things might require more sophisticated solutions, I guess. Personally, I was just caught off guard, since it wasn’t in the (WIP) changelog.
@U09FL65DK Is there a particular category of bad code I should be looking for when "Index 2 out of bounds for length 0"
-style errors occur?
cljs code should work in an e/client
block, the exception I know for now is #js
literals. I mean they work on cljs side but the clj reader doesn't have a tag reader for it.
index out of bounds runtime error is usually a symptom of bad macroexpansion, i.e. the server expanded the code differently than the client