Fork me on GitHub
#hyperfiddle
<
2024-03-20
>
henrik08:03:35

I’m using an atom client side to contain a paginated list of items. When it loads, it contains 10 items on page 1. When the user navigates to page 2, 10 more items are loaded and added, and so on. This means that were they to go back to page 1, those 10 items are available immediately. This is conditional of a category selection. So all items loaded are in a specific category. This means that if the user switches category, the cache atom now contains all items from the previous category, plus 10 new items. It’s a side effect of being stateful, and a classic and major downside of doing it this way. What happens currently is that all items not belonging to the current category is cleared out of the atom on switch, and this works. In React, the same problem would probably be solved by giving the root component a key based on the current category, which would cause React to tear down the current state and rebuild it, thus effectively masking the statefulness happening in it. Not that I want to use it, but is there a mechanism like this in Electric? Can you make an arbitrary tree conditional on some hash?

Dustin Getz09:03:43

for-by can be wrapped i.e. put a single child branch in a collection size 1 and manage it with a key

👀 1
Dustin Getz09:03:37

i also needed this at least once, maybe it should be made public api

henrik09:03:52

Ah, that’s clever. It feels like something one shouldn’t make a habit out of. But for clearing out e.g. states holding settings etc. that specifically controls views, and doesn’t really rely on data streamed from the DB etc., it could be an acceptable pattern to reset to defaults reliably.

henrik09:03:50

A function that wraps it could be convenient.

henrik10:03:58

An attempt:

(defmacro given
  [key & body]
  `(first (e/for-by identity [_k# [~key]]
            ~@body)))
(given something
  (dom/div (dom/text "Hello"))
  (e/on-unmount #(println "Torn down")))

henrik10:03:50

Not sure would it should be called. But theoretically then, given a change in something , “Torn down” should be printed though there are no dependencies.

henrik13:03:49

This works as expected. I ended up rewriting it as,

(defmacro while
  "Given a truthy `key`, executes the reactive `body`.
   `body` re-executes for each distinct value of `key`."
  [key & body]
  `(first (e/for-by identity [k# [~key]] (when k# ~@body))))
It’s semantically tenuous, but clojure.core/while felt like the closest thing to map the behavior to.

Vincent18:03:33

I enjoy your macro and wonder about the perfect name

henrik08:03:18

Thanks! I guess it’s more Dustin’s while than mine though, since he suggested the core mechanic. It’s interesting, for example

(e/defn TestWhile
  []
  (e/client
    (let [!a (atom 10)
          a  (e/watch !a)]
      (while (and (pos? a) a)
        (println "Hello")
        (swap! !a dec)))))
If this while is cljs.core/while, this prints “Hello” ~100 times and then crashes the app. If it is the while above, it prints “Hello” 10 times and then stops, with no crash. It makes sense why the core while crashes the app, but incidentally the bespoke while exhibits behavior closer to how while works outside of Electric.

Dustin Getz12:03:30

what is the crash from core/while?

henrik12:03:42

I didn’t pay attention to the exact message, let me have a look

henrik12:03:29

On the client, Websocket is already in CLOSING or CLOSED state .

henrik12:03:40

On the server

henrik12:03:23

But I’m basically DOSing myself, so this isn’t super surprising IMO

Dustin Getz12:03:49

agree it's not surprising, but it still might be a bug that it throws NPE (though certainly the behavior isn't defined)

Vincent13:03:38

do-when is what I want to call it, in certain contexts

Andrew Wilcox02:03:30

Very cool. I think that the name while doesn't quite capture the idea: the important functionality this provides is to remount the body when the key changes. I suggest perhaps distinguishing

milelo11:03:30

I've created an https://github.com/milelo/electric-multi-client-app, a minimal Electric Clojure app based on https://github.com/hyperfiddle/electric-starter-app providing a server with multiple independent clients, routing and an https://v1-docs.xtdb.com/guides/in-a-box/ database. Each client has its own dependencies and electric reactor.

💪 3
❤️ 2
👍 1
braai engineer18:03:37

How should I go about securing my Electric routes while exposing some public routes on the same server? In the starter app template, the recommended place to inject (wrap-authentication) like basic auth is in electric-websocket-middleware, which fires before anything else, returning 401 for all routes. Is it possible to add auth middleware only to Electric websocket upgrades?

Dustin Getz18:03:12

Have you considered using an if statement at the top of your electric app?

braai engineer18:03:49

I want to exclude auth from some non-Electric routes, e.g. /api/do-thing, and /public-thing.html

braai engineer18:03:56

Otherwise I would do it all in Electric

braai engineer18:03:56

Deploying two different apps on two different domains or ports is an option, but then I have to deploy two things.

Dustin Getz18:03:25

Oh ok, that seems like a ring question, surely it is possible, i would find the ring support channels and ask there

braai engineer18:03:54

yeah I guess my question becomes: can I safely move the electric websocket detection to later in the middleware pipeline, or will content-type stuff mess with it?

nakkaya22:03:43

If you search my messages I should have posted examples on this. In my case electric app lives in /app and authenticated and rest of the routes are unauthenticated.

👍 1
milelo09:03:24

You could secure it externally (for free) through https://www.cloudflare.com/en-gb/products/tunnel/