This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-03-14
Channels
- # aleph (4)
- # announcements (10)
- # babashka (21)
- # beginners (67)
- # biff (7)
- # calva (4)
- # clojure (40)
- # clojure-europe (11)
- # clojure-gamedev (17)
- # clojure-losangeles (3)
- # clojure-madison (1)
- # clojure-nl (1)
- # clojure-norway (78)
- # clojure-uk (3)
- # clojurescript (83)
- # core-typed (18)
- # cursive (1)
- # datalevin (2)
- # datomic (2)
- # gratitude (2)
- # hyperfiddle (56)
- # introduce-yourself (1)
- # london-clojurians (1)
- # matcher-combinators (10)
- # membrane (161)
- # polylith (16)
- # portal (4)
- # reitit (4)
- # releases (3)
- # ring (2)
- # shadow-cljs (9)
- # squint (2)
- # timbre (10)
- # xtdb (14)
- # yamlscript (1)
My app has a glitch. I have a list of data items, a search string input and a search status display (blank input / found / not found). The search status has a background color to indicate the state, and it briefly but noticeably flashes a wrong color when the user changes the search string from empty to “1” (for example). I have a minimal repro — see details in my in-thread reply.
If the input is an integer between 1 and 100, that’s regarded as :found
. An empty input is :blank
, and the other alternative is :not-found
.
When the user changes the input from an empty string to “1”, I get the following output (with an unwanted :not-found
that is causing my glitch):
---------
:not-found "1"
search-str = "1"
:found "1"
I can make the glitch go away if I do any of the following:
• move the body of FoundStatus
to the call site
• wrap the call of FoundStatus
in (e/server ...)
• remove the (e/client ...)
wrapping of the call of FoundStatus
.
In my actual app I haven’t found a simple workaround that works, and in any case there’s the possibility that when I refactor I would re-introduce the glitch.
I guess this is a bug, but is there something I can do to work around this? Maybe a way of saying to only pass on a reactive value if it’s had the same value for a certain amount of time?
I think this is what we call the "when true" bug, I will need to look more closely to be certain
I said:
> In my actual app I haven’t found a simple workaround that works
FWIW, that’s not true. I’ve wrapped the call of FoundStatus
in (e/server ...)
and all is good.
one trick that often helps, is this idiom: (case found? (FoundStatus. search-str found?))
– the case
forces found?
to resolve (not be Pending) before evaluating the body. I'll need to dig into our issue tracker to remember the exact details of the bug
to be clear, if the bug is what i think it is, it should just be fixed, and i dont know if the (case x ...)
pattern has real world use once Electric is perfected. This particular bug won't be fixed until Electric v3, it was a deep issue
Thanks for the suggestion and additional details.
I’ve tried (case found? (FoundStatus. search-str found?))
, but it makes no difference. 😞
I think hyperfiddle could benefit by having an options-ui. The idea by options-ui is to declarative specify which options should be collected from the user and which choices are allowed.
this sounds a bit like HFQL, which is a powerful hypermedia DSL described on the new website - https://www.hyperfiddle.net/
in fact the reason we built Electric was to make HFQL possible
not public yet
The benfit of that is that one can have different ui elements (textbox; combobox; date-picker; button; user defined elements) and so this options then can be used to make requests to clj or just to render ui differently.
Another idea how electric could become easier to use is to bring in Tlthe concept of extensions. An extension is something that adds features to electric without having to do any configuration. The idea is that once you add an extension to the classpath by having it included as a dependency then it will automatically add it to the cljs build. This makes it a lot easier to reuse frontend ui components. An example is here: https://github.com/pink-gorilla/ui-highcharts/blob/master/resources/ext/ui-highcharts.edn
So having a resource that matches #"ext/.*/.edn" it woudl read the content: {:name "ui-highcharts" :cljs-namespace [ui.highcharts]} and in this case it would add ui.highcharts to the shadow build.
does anyone have css hot reloading working?
.css artifacts hot reload via shadow which i believe works out of the box in the starter app, what are the specifics of your setup?
I was just trying to throw css into the index page and see if it would hot reload but doesn’t seem to be. I just forked the starter app and tried similarly and its not working.. I’ll push a branch really qucik
This is what I tried to add to starter app https://github.com/kurtharriger/electric-clojure-state-issue/commit/6c3269ba4627cb13c0be1454a134aa41adf4874b
you're right, the starter app may be broken, i'll log a ticket
starter app didn’t include any css so not really broken but an example would help
changing to watch-dir seems to have fixed it
I’ll send PR
Thank you for this!
Hi, in the below program, there is a reactive input that starts at directory c:/tmp/
which has 5 files as returned from the server. There are two println
forms that print the current dir
and cnt
of files to both server and client respectively the both print c:/tmp/
and 5
count of files at startup as expected (the c:/tmp/
directory has 5 files in it). When the user appends the letter e
to the input field (i.e. c:/tmp/e
) the server looks at that c:/tmp/e
directory and returns 0 files (the directory is empty). So the expected output of the program when this happens should be c:/tmp/e
and 0
files for both server and client, though on the client side I'm getting a print out of c:/tmp/e
dir and 5
files first, and then the expected c:/tmp/e
and 0
files. I was expecting the client println form to only fire once with c:/tmp/e
and 0.
Why is that so?
(ns electric-starter-app.main
(:require [hyperfiddle.electric :as e]
[hyperfiddle.electric-dom2 :as dom]
[hyperfiddle.electric-ui4 :as ui]))
;; Saving this file will automatically recompile and update in your browser
#?(:clj
(defn dir-list [dir]
(.listFiles ( dir))))
(e/defn Main [ring-request]
(e/client
(binding [dom/node js/document.body]
(let [!dir (atom "c:/tmp/")
dir (e/watch !dir)]
(ui/input dir (e/fn [v] (reset! !dir v))
(dom/props {:type "search"}))
(e/server
(let [files (dir-list dir)
cnt (count files)]
(println :dir dir :files cnt)
(e/client
(println :dir dir :files cnt))))))))
;; server output
;; :dir c:/tmp/ :files 5
;; :dir c:/tmp/e :files 0
;; client output
;; shadow-cljs: #3 ready!
;; :dir c:/tmp/ :files 5
;; :dir c:/tmp/e :files 5 ;; didn't expect this
;; :dir c:/tmp/e :files 0
more details at https://github.com/hyperfiddle/electric/issues/68Hi - thanks
What seems to be happening here is you expect the dir
watch to be transactional with the cnt
derived value
the dir
watch is on the client, and cnt
is on the server, so we're seeing latency, but you reasonably expect (println :dir dir :files cnt)
to only run after all parameters are available
right, I was expecting the same semantics as if I was reading a plain clojure without the distiction of client/server (apart from the atom reactiveness)
or put it perhaps differently, I was expecting the value of dir and cnt to be synchronized, given that cnt
is dependent of dir
I need to confirm with the team (Electric v3 is sharpening a bunch of stuff). Broadly the consistency model is such that client-internal propagations result in consistent states as guaranteed by missionary, and same for server-internal propagations, but for distributed propagations there are some tradeoffs, but it does IIUC seem like the Pending cnt
and the dir
should be known simultaneously and therefore the println
should not ever see that inconsistent state
See what would happen if you enforce a synchonization point before the print:
#?(:clj (defn search
[dir]
(let [files (dir-list dir)]
{:dir dir
:files files
:cnt (count files)})))
(e/defn Main [ring-request]
(e/client
(binding [dom/node js/document.body]
(let [!dir (atom "c:/tmp/")
dir (e/watch !dir)]
(ui/input dir (e/fn [v] (reset! !dir v))
(dom/props {:type "search"}))
(e/server
(let [{:keys [dir cnt]} (search dir)]
(println :dir dir :files cnt)
(e/client
(println :dir dir :files cnt))))))))
In this case, I think you should see consistent output, since the Clojure function would ensure that the count changes as the dir changes, rather than independently of each other.@U012BL6D9GE I spoke with the team. We believe this is exactly a known and previously resolved issue we call the "distributed glitch", which is: when cnt
should be pending because it is derived from dir
via the network. Electric v2 already accounts for this circumstance and therefore the behavior you observe is unexpected and we have logged it as a bug.
Thanks @U09K620SG for looking into this, and I can confirm @U06B8J0AJ workaround should also work (I've tried something similar in my code at the time).
Just trying to set my expectations on the bindings resolution across boundaries, because it caught me by surprise.
In a even more simplified example below, I was similarly expecting the program to be read from top to bottom like a "normal" clojure program, with electric taking care of efficiently transporting the values under the hood as need. If a reactive change happens at the higher form, the flow continues synchronously to the inner forms where the variables are affected.
The program starts by sleeping for 100 ms
amount of time on the server, and then reports on it from both sides. When the button is pressed, the sleep amount is increased by 1, and the reactive process is triggered once more.
There's an extra reporting on the client, that says that ms
is 101 but the server-msg
is slept for 100 ms, which breaks my expectations.
If I am not to reason about the program as one that the flow of information is sequential and bindings dependencies are maintained in the flow across the boundaries, how should I expect the reactive update part to behave? Am I responsible forcing synchronization of the bindings across boundaries? I would have thought electric has all the information required to do so in the graph but it might as well be a hard problem to solve automatically, given the nature of the setup with n-clients communicating with a single server.
(ns electric-starter-app.main
(:require [hyperfiddle.electric :as e]
[hyperfiddle.electric-dom2 :as dom]
[hyperfiddle.electric-ui4 :as ui]))
;; Saving this file will automatically recompile and update in your browser
#?(:clj
(defn sleep [ms]
(Thread/sleep ms)
[:slept-for-ms ms]))
(e/defn Main [ring-request]
(e/client
(binding [dom/node js/document.body]
(let [!ms (atom 100)
ms (e/watch !ms)]
(ui/button (e/fn []
(println :client :sleep-increase)
(swap! !ms inc))
(dom/text "inc sleep"))
(println :client :sleeping-for ms)
(e/server
(let [server-msg (sleep ms)]
(println :server :sleep-for-ms ms :server-msg server-msg)
(e/client
(println :client :sleep-for-ms ms :server-msg server-msg))))))))
;; client output
;; => Connecting...
;; => shadow-cljs: #7 ready!
;; => Connected.
;; => :client :sleeping-for 100
;; => :client :sleep-for-ms 100 :server-msg [:slept-for-ms 100]
;; button pressed
;; => :client :sleep-increase
;; => :client :sleep-for-ms 101 :server-msg [:slept-for-ms 100] ;; I wasn't expecting this to trigger
;; => :client :sleeping-for 101
;; => :client :sleep-for-ms 101 :server-msg [:slept-for-ms 101]
;; server output
;; => :server :sleep-for-ms 100 :server-msg [:slept-for-ms 100]
;; button pressed
;; => :server :sleep-for-ms 101 :server-msg [:slept-for-ms 101]
I don't understand your question, that is a lot to unpack
Electric maximizes concurrency, it is not sequential/imperative evaluation
the line you commented looks like the same glitch as your original report, it is a bug
the sleep function is also wrong because it is synchronous blocking, it needs to be an async missionary sleep
Yeah, sorry not being clear. I'm just trying to understand how to reason about the program. My expectation was that I can read the program the "same" as a clojure program, and that is sequentially, in which case that line that I mentioned above shouldn't have happened. If this is also due to the "glitch" then I am covered, the program can be reasoned by me as if it was sequeantal then and this is how I expect it to run. I thought the "glitch" described earlier, was when trying to send two bindings across from the server, in this case is only one.
The use of sleep is just for demonstration purposes, I just wanted to have a a server funtion that will take "some" time to complete (i.e. doing I/O), and sleep was the easiest to use to make my point
what I mean by sequental evaluation in the presence of reactivity is how the various forms are triggered. So when the program starts, I expect the following "sequential" evaluation of bindings: !ms->ms->(println :client :sleeping-for ms)->(sleep ms)->server-msg->server println->client println, which does happen I would have expected the same flow to happen after the button is pressed, but I am getting the extra print out. And thus the question. Though now you have said that this is the same bug as before, I'm covered.
Btw, is there a way for me to track the issue, maybe as a github issue, or shall I periodically look at master checkins?
That sounds right, the evaluation order should exactly match the dataflow DAG, effects run when one or more inputs change and you should never see inconsistent states
With respect to the issue, we've added it to our internal tracker and will look when we have time