Fork me on GitHub
#hyperfiddle
<
2024-02-23
>
prabhasp14:02:39

Hi all, beginner here - working with electric and datomic, and having an issue where I'm writing to the database excessively. What am I doing wrong? Code in the thread.

prabhasp14:02:43

I am working off of the latest electric-starter-app. Db refresh is setup like this:

;; db.cljc
(defonce !db (atom nil))
(def conn)
with a modification to -main as follows
;; dev.cljc
#?(:clj ;; Server Entrypoint
  (do
   (def config
    {:host "0.0.0.0"
    :port 8080
    :resources-path "public/electric_starter_app"
    :manifest-path ; contains Electric compiled program's version so client and server stays in sync
    "public//electric_starter_app/js/manifest.edn"})

   (defn -main [& args]
    (log/info "Starting Electric compiler and server...")

    (alter-var-root #'conn (constantly (start-datomic!)))
    (reset! !db (d/db conn))
    ;; datomic tx queue allows only single consumer
    ;; we spawn a single consumer here and feed the new db into an atom
    ;; clients can now watch this atom
    (future (let [q (d/tx-report-queue conn)]
         (loop []
          (reset! !db (:db-after (.take ^java.util.concurrent.LinkedBlockingQueue q)))
          (recur))))

    (shadow-server/start!)
    (shadow/watch :dev)
    (comment (shadow-server/stop!))

    (def server (jetty/start-server!
           (fn [ring-request]
            (e/boot-server {} electric-starter-app.main/Main ring-request))
           config))

    (comment (.stop server)))))
My app code:
;; main.cljc

(e/def db)

...

(e/defn View-book [book-id]
 (e/server
  (let [book (d/entity db book-id)
     book-title (:book/title book)
     ; [page-id :page/text :page/number]
     pages (sort-by last
         (d/q '[:find ?page ?text ?number :in $ ?book-id :where [?page :page/book ?book-id] [?page :page/text ?text] [?page :page/number ?number]] db book-id))
     max-page-number (apply max (conj (map last pages) 0))]
   (try
    (e/client
     (dom/h1 (dom/text book-title)))
    (e/for-by first [[page-id page-text page-number] pages]
     (e/client
      (dom/div
       (dom/text
        (str page-text " (" page-number ")")))))
    (e/client
     (dom/input (dom/props {:placeholder "Page text"})
      (dom/on "keydown"
       (e/fn [e]
        (when (= "Enter" (.-key e))
         (when-some [v (empty->nil (.substr (.. e -target -value) 0 400))]
          (e/server
           (d/transact conn [{:page/text v :page/number (inc max-page-number) :page/book book-id}])))
         (set! (.-value dom/node) ""))))))
    (catch Pending e (println "Loading..."))))))

(e/defn Main [ring-request]
 (e/server
  (binding [db (e/watch !db)]
   (e/client
    (binding [dom/node js/document.body]
     (let [state (e/watch state!)
        selected-book (:selected-book state)]
      (List-books.)
      (if (some? selected-book)
       (View-book. selected-book))))))))
The issue is that when I hit enter on the input, that input is saved many times, as you can see in the screenshot. Why? (I can see when we call transact, we update the watched db, which causes the d/transact to re-run. If that loop is expected, what causes this to stop?) And what's a better pattern to use that doesn't cause an issue like this?

Dustin Getz15:02:46

(without looking closely at your code) you're right that it's a cycle, e/snapshot can be used to break cycles for now. We're working on a new pure functional pattern but for now you need to understand e/snapshot. There are many threads discussing

👀 1
Dustin Getz15:02:49

the d/transact side effect references max-page-number which is derived from db , there may be others

Dustin Getz15:02:25

> what causes it to stop you're racing the network latency, dom/on takes an e/fn (a "component" i.e. "piece of DAG") as a "callback"), dom/on has complicated machinery (a state machine) inside which mounts and unmounts the e/fn, managing it's lifecycle. the FSM is on the client and waiting for the first non-Pending result from the e/fn, which throws Pending at the e/server point. This is racing against the cycle between d/transact and max-page-number – and note that cycle is server-only, so you get several iterations before the client sees a non-Pending result and then unmounts the frame and tells the server to unmount as well.

Dustin Getz15:02:07

we hate the pattern, it is a "good enough in practice" until we can release a pure-functional flow-based way to deal with events – which is blocked on Electric v3 which contains upgrades needed to do this

👀 1
prabhasp17:02:04

Thanks, that makes sense. Will try it out!