Fork me on GitHub
#hyperfiddle
<
2023-05-17
>
J08:05:07

Hi! I create a https://github.com/jeans11/electric-datomic-starter (based on the electric-xtdb-starter). I notice a little mistake when there are multiple clients. Looks like the db is not the same between the two clients (cf. video in thread)

2
xificurC09:05:21

I suspect each client creates a new tx-queue reader and the first one that manages to .take from the queue wins. You might need to turn the db queue reads into a singleton. Maybe toplevel (e/def db (new (db/latest-db> user/!conn user/!tx-queue)))

J09:05:30

Nevertheless, the !tx-queue is like the !conn , both are created once.

J09:05:56

But I understand your point

J10:05:02

Tried (e/def db (new (db/latest-db> user/!conn user/!tx-queue))) on toplevel but same result.

xificurC10:05:16

how do you run datomic? I want to test this locally

J11:05:01

In memory.

J11:05:21

I have tested with the transactor. Same result

xificurC11:05:52

running main I get

Execution error (ExceptionInfo) at datomic.error/raise (error.clj:70).
:db.error/db-not-found Could not find db in catalog
Did you do any setup I have to?

J11:05:02

Oups sorry! I create the database manually and transact schema after the connect.

xificurC14:05:42

can you provide the exact code you use to initialize? To have the same setup and prevent errors

J14:05:23

I updated the repo. Now just launch user/main

jjttjj18:05:30

I just started playing with electric and came here to ask how to setup a datomic connection so that things would be updated reactively when it changes, thanks for this, it's exactly what I was going to ask!

xificurC20:05:13

didn't get time to look at this, I'll try tomorrow. Thanks for the commit J!

J20:05:56

No problem

xificurC08:05:51

I sent you a PR with a fix

🎉 2
J10:05:12

Thanks @U09FL65DK you rocks!

jjttjj15:05:45

Thanks both of you, I've been using this as a guide to use a datomic db to keep state for my electric app, now with the new PR changes. This isn't related to the problem described (it happened before and after I applied the PR changes) but I'm curious about this. Assuming db here is bound to an atom containing the latest datomic db value, which should be reset on each tx, is there an obvious reason that d/entity version isn't triggering a ui refresh?

(let [current-chat
      ;; doesn't work, a change to the user/current-chat doesn't trigger a ui change
      (e/server
        (-> (d/entity db [:user/id 0])
            :user/current-chat
            :chat/id))
      ;; this does work (a change to the current-chat is reflected in the ui)
      #_(e/server
          (->
            (d/pull db [{:user/current-chat [:chat/id]}] [:user/id 0])
            :user/current-chat
            :chat/id))]
  ...)

2
xificurC15:05:08

it's a duplicate of this https://github.com/tonsky/datascript/issues/433 in datomic. I think @U09K620SG logged a ticket with the datomic team

jjttjj15:05:39

Ah, thanks!

braai engineer09:05:43

How do I make an Electric component (signal function) that takes an e/fn and runs it correctly? I have a tree picker component that takes a value and an on-change handler, like so: (e/defn TreePicker [value on-change] …). I want the on-change handler to do do something on the server, but the following does not seem to work:

(dom/div
  (TreePicker. value (fn [new-value] (e/server (side-effect! node new-value)))
Do I need to call (new (e/fn [] …) ?
(dom/div
  (TreePicker. value (fn [new-value] (new (e/fn [] (e/server (side-effect! node new-value)))))
How do I support signal fn’s as args in my Electric “components”?

2
xificurC09:05:15

(fn [new-value] (new (e/fn ... this is undefined. It's like writing clojure code in a java method. You're writing electric code in a clojure function. Does that make sense?

xificurC09:05:34

if you need electric code it needs to be an e/fn. You can pass it as an argument and call it with new

xificurC09:05:05

(TreePicker. value (e/fn [new-value] (e/server (side-effect! node new-value))))

braai engineer09:05:05

OK so I can pass an e/fn as my SignalArg and call (new SignalArg arg1 arg2 …)? will try

xificurC09:05:39

and TreePicker has to call (new on-change ...)

xificurC09:05:20

we also have a convention to uppercase electric functions, makes it easier to remember when to call new

(e/defn TreePicker [value OnChange] ...)

braai engineer12:05:44

@U09FL65DK why is (fn [new-value] (new (e/fn [] …)) undefined? Can I do (fn [new-value] (new SomeSignal arg1))? Why is that valid? Is it because the e/fn needs to be defined in root, i.e. can’t be built at “runtime”?

xificurC14:05:59

both of your examples are invalid. You're essentially writing the equivalent of "clojure-in-java"

public void foo() {
  (clojure-code-here)
}
but can't see it because electric is a lisp built on top of clojure

👍 2
xificurC14:05:37

clojure fn = clojure land, electric not available, you dropped a layer

braai engineer14:05:32

You said earlier that “TreePicker has to call (new on-change …)” where on-change is an Electric fn, but that can’t happen in a normal Clojure fn? So it has to be Electric turtles all the way down? And it only works because TreePicker is e/defn in Electric land? Just to confirm, it’s not possible to run a signal fn (if I’m using the right term, or Electric fn) from a normal Clojure function at all?

xificurC14:05:00

yes yes yes 🙂

Dustin Getz20:05:25

calling into a reactive function from a non-reactive function hits the function coloring problem - like async/await

👍 2
braai engineer23:05:49

I have a floating picker component that pops up when you click on a particular column for any row in a table. Tricky thing is closing any open pickers when I click on the surrounding div. Annoying to keep track of all !open-pickers (atom #{123 456}) in some global state. Gotta pass the state around, swap! when you click, propagate state back down - kinda messy but at least it’s central. There are tricks like using the focus/blur events on a text input inside the picker and then preventing propagation of any click events inside the control. So if you click outside, the control blurs and you close the picker. But there are always edge cases and you have to do a cowboy dance to maintain focus in the text input. Is there a more way convenient way “reach into” Electric components and reset! an atom in all of them, or send them all a signal conveniently? Or maybe “spawn” a new picker when you click on the column that can, I dunno, somehow subscribe to other click or focus events, so it can be closed? Probably a tale as old as time for floating controls.

Dustin Getz01:05:02

the question is how to close an open picker by clicking away?

xificurC07:05:37

IIUC the question is how to close all open pickers in 1 swoop, correct? How about

(def !picking? (atom false))
(e/def picking? (e/watch !picking?))
and have the pickers wrapped with a (when picking? ...)? Then you can close all of them with a single (reset! !picking? false)

xificurC07:05:38

I hear your pain with the focus handling, our pickers are also struggling through this. I don't recommend looking at them right now since they are midway through a rewrite and rely on some undefined behavior, but the current strategy there is to have a div covering the entire screen with an increased z-index with the picker on top of that. Then clicks outside the picker land in this div and you can unmount the picker

Dustin Getz10:05:38

you could also try datascript for top level state rather than lots of atoms

braai engineer20:05:03

Update on this: current solution is to catch any clicks on surrounding (background) divs, and closes any open flaoting pickers. Any open floating pickers must call (.stopPropagation e) on click to prevent the background from closing open pickers, but naturally this means you have to keep track of all open pickers globally so the background divs know what to close. It would be neater if the DOM allowed a way for divs on top (z-index wise) to be notified of clicks outside, or to have something like focus events on divs that wasn’t tied to text inputs, because then the component could close itself when it went “out of scope.”

jjttjj15:05:45

Thanks both of you, I've been using this as a guide to use a datomic db to keep state for my electric app, now with the new PR changes. This isn't related to the problem described (it happened before and after I applied the PR changes) but I'm curious about this. Assuming db here is bound to an atom containing the latest datomic db value, which should be reset on each tx, is there an obvious reason that d/entity version isn't triggering a ui refresh?

(let [current-chat
      ;; doesn't work, a change to the user/current-chat doesn't trigger a ui change
      (e/server
        (-> (d/entity db [:user/id 0])
            :user/current-chat
            :chat/id))
      ;; this does work (a change to the current-chat is reflected in the ui)
      #_(e/server
          (->
            (d/pull db [{:user/current-chat [:chat/id]}] [:user/id 0])
            :user/current-chat
            :chat/id))]
  ...)

2