Fork me on GitHub
#hyperfiddle
<
2024-03-07
>
Andrew Wilcox09:03:27

Is it possible to use plain dynamic binding with plain Clojure values on the server? I'm keeping some state on the server for each connection. I might like to do something like:

#?(:clj (def ^:dynamic !client-state))
(e/def client-state)

(e/defn Main [ring-request]
  (e/server
   (binding [!client-state (atom {})]
     (binding [client-state (e/watch !client-state)]
       ...)))))
This fails because !client-state isn't an e/def. Of course I can make it an e/def:
(e/def !client-state)
(e/def client-state)

(e/defn Main [ring-request]
  (e/server
   (binding [!client-state (atom {})]
     (binding [client-state (e/watch !client-state)]
       (UI.)))))
I'm not sure if this is a hack, or a natural way of lifting a Clojure value into the Hyperfiddle DSL using a constant reactive value 🙂

henrik10:03:55

That looks good to me. You can put multiple bindings in binding , so no need to nest them.

Andrew Wilcox10:03:06

I don't know about the hyperfiddle DSL, but my understanding was that Clojure's binding was executed in parallel.

henrik10:03:30

Oh yeah, you’re right.

henrik11:03:43

If anyone is interested, we’re employing a React-inspired macro to declare state. E.g.,

(let [[current-count set-count!] (use-state 0)]
  (dom/div (dom/text (str current-count)))
  (dom/button
    (dom/text "Increment")
    (dom/on! "click" #(set-count! inc)))
  (dom/button
    (dom/text "Reset")
    (dom/on! "click" #(set-count! 0))))

👀 1
👍 2
Dustin Getz13:03:53

What do you think is the benefit over just using the native clojure atom ops?

henrik13:03:45

Three minor things that just about add up to being worth it (in our opinion): • Slightly more convenient to write, as the atom itself is inconsequential in 99% of cases: you want its value and a way to manipulate it. • In the same vein, you have a setter that by virtue of the interface gets named closely to what it does. So, slightly cleaner semantics and a more direct way to get to them. • The shape and function is familiar to React users, or users of UIx and other libs that follow the React form and function.

👍 1
henrik13:03:26

Similarly, we have a $ macro as well (inspired by UIx). So the above might look something like this in our codebase:

(let [[current-count set-count!] (use-state 0)]
  ($ :div ($ :text (str current-count))
    ($ Something {:hello :world})
    ($ :button {:class […]}
      ($ :text "Increment")
      (dom/on! "click" #(set-count! inc)))
    ($ :button {:class […]}
      ($ :text "Reset")
      (dom/on! "click" #(set-count! 0)))))
I.e., $ used for both instantiating reactive functions and dom elements, and to elide dom/props when the payload is a straight map (which it is most of the time).

danbunea13:03:57

I always have the atoms holding the state as more complex maps, and the changes that I do to them I always extract in separate functions.