This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-02-23
Channels
- # announcements (18)
- # beginners (26)
- # calva (12)
- # cider (43)
- # cljdoc (4)
- # clojure (38)
- # clojure-europe (11)
- # clojure-nl (1)
- # clojure-norway (12)
- # clojure-sweden (2)
- # clojure-uk (4)
- # cursive (17)
- # data-science (4)
- # datalevin (2)
- # datomic (3)
- # emacs (10)
- # ghostwheel (4)
- # graphql (11)
- # honeysql (1)
- # hyperfiddle (7)
- # introduce-yourself (1)
- # malli (23)
- # nrepl (11)
- # overtone (1)
- # pathom (9)
- # pedestal (2)
- # polylith (1)
- # portal (3)
- # reitit (1)
- # shadow-cljs (12)
- # timbre (4)
- # vim (2)
- # xtdb (4)
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.
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?(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
the d/transact
side effect references max-page-number
which is derived from db
, there may be others
> 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.
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