Here's a playground for a new project announced on ClojureVerse called standard-clojure-style which formats Clojure code: https://squint-cljs.github.io/squint/?src=KHJlcXVpcmUgJ1siaHR0cHM6Ly9lc20uc2gvQGNocmlzb2FrbWFuL3N0YW5kYXJkLWNsb2p1cmUtc3R5bGUkZGVmYXVsdCIgOmFzIGNsai1zdHlsZV0pCgooZGVmb25jZSBwcmUgKGRvdG8gKGpzL2RvY3VtZW50LmNyZWF0ZUVsZW1lbnQgInByZSIpCiAgICAgICAgICAgICAgIChzZXQhIC1pZCAiYXBwIikKICAgICAgICAgICAgICAgKGpzL2RvY3VtZW50LmJvZHkucHJlcGVuZCkpKQoKOzsgbm90IGluZGVudGVkIG9uIHB1cnBvc2UgdG8gc2VlIGlmIHN0YW5kYXJkLWNsb2p1cmUtc3R5bGUgaW5kZW50cyBpdDoKICAoKyAxIDIgMykKCihzZXQhIGpzL2FwcC5pbm5lclRleHQKICAoLT4gKGNsai1zdHlsZS9mb3JtYXQgKC0%2BIChqcy9lZGl0b3Iuc3RhdGUuZG9jLnRvU3RyaW5nKSAudHJpbSkpCiAgICA6b3V0KSk%3D
Thanks for creating this awesome t-shirt, @borkdude! 🙏 https://x.com/pappapez/status/1848673030970520005?s=46&t=2szMNNiUUdq3HoUKFCutLg
\m/
made a more fleshed out example for jotai/react https://github.com/yogthos/squint-jotai-example/blob/main/src/App.cljs
I'm using jotai atom as the global state, akin to how you'd use re-frame, and then React useState for the ephemeral component state, seems to work pretty well
Interesting. What's the utility of the anime/setAnime over deref/dispatch? Does it keep the read/writes more coordinated in some way?
Or I guess another way to ask - if it were possible to put a deref/dispatch idiom over jotai's api, would that be a good idea or a bad idea? And why?
I think slapping deref/dispatch over jotai would work fine, and jotai also supports cursors
so you effectively have reagent semantics with it
nice
I find the biggest advantage of reagent approach over hooks is that the state of the data isn't coupled to the DOM tree, and you can inspect it any time you want, you also don't have to jump through hoops if you have two components in a tree that need to share state
yeah, same
@yogthos the same author also has a different state handling library called zustand: https://github.com/pmndrs/zustand not sure what the trade-offs are
admittedly, I only skimmed the other lib, but I get the impression that jotai fits better with clj semantics
yeah, but perhaps there's performance considerations. not sure. I also noticed that zustand works with immer as well
that could be handy, but yeah not sure what the implications are for bigger apps
it does look like jotai also has immer extension btw https://jotai.org/docs/extensions/immer
right
and just tried it, looks like it works seamlessly
(ns App
(:require
["jotai" :as jotai :refer [useAtom]]
["jotai-immer" :as jotai-immer :refer [atomWithImmer]]
["react" :refer [useState]]))
(def !anime (atomWithImmer
[{:title "Ghost in the Shell"
:year 1995
:watched true}
{:title "Serial Experiments Lain"
:year 1998
:watched false}]))how do you mutate the atom?
the example still uses useAtom according to the docs immer atom provides a different writeFunction which I assume leverages structural sharing?
I mean, how do you change the value
oh that's same as before
(let [anime setAnime] (useAtom !anime)]
...)like useState
the setter function handles the updating
nice
although now I'm curious if you can update a specific value this way since setter just takes a whole new state
so the setter function does the draft stuff
and then you should just use the mutation ! functions from squint
so it's basically a swap?
yeah looks like
specific value, do you mean like a cursor?
I wonder how hard it would be to make a little wrapper around jotai + immer that provides reagent semantics 🙂
I was thinking something like (swap! foo assoc :bar :baz)
well immer expects you to really mutate the value
since it watches it via some proxy layer
so I guess here it would be
(setAnime (assoc! anime :foo :bar))
so you'd use assoc! and conj! etc
and magic happens? 🙂
yes, this is why I asked how you did it ;)
here's an immer demo:
https://squint-cljs.github.io/squint/?src=KHJlcXVpcmUgJ1siaHR0cHM6Ly9lc20uc2gvaW1tZXJAMTAuMC4zIiA6YXMgaW1tZXJdKQoKKGRlZm4gYXBwZW5kIFtzdHJdCiAgKGpzL2RvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQKICAgKGRvdG8gKGpzL2RvY3VtZW50LmNyZWF0ZUVsZW1lbnQgImRpdiIpCiAgICAgKHNldCEgLWlubmVyVGV4dCAoanMvSlNPTi5zdHJpbmdpZnkgc3RyKSkpKSkKCihkZWYgYmFzZS1zdGF0ZSBbezp0aXRsZSAiTGVhcm4gU3F1aW50ISIKICAgICAgICAgICAgICAgICAgOmRvbmUgdHJ1ZX0KICAgICAgICAgICAgICAgICB7OnRpdGxlICJUcnkgSW1tZXIiCiAgICAgICAgICAgICAgICAgIDpkb25lIGZhbHNlfV0pCgooZGVmIG5leHQtc3RhdGUgKGltbWVyL3Byb2R1Y2UgYmFzZS1zdGF0ZQogICAgICAgICAgICAgICAgICAoZm4gW2RyYWZ0XQogICAgICAgICAgICAgICAgICAgIChhc3NvYy1pbiEgZHJhZnQgWzAgOmRvbmVdIHRydWUpCiAgICAgICAgICAgICAgICAgICAgKGNvbmohIGRyYWZ0IHs6dGl0bGUgIlR3ZWV0IGFib3V0IGl0In0pKSkpCgooYXBwZW5kIGJhc2Utc3RhdGUpCihhcHBlbmQgbmV4dC1zdGF0ZSk%3D
the array with maps is mutated using conj! , yet the previous value is unchanged
that's the idea of immer
mmm, looks like setAnime would take a function that takes the draft
it looks like you can pass a draft to it as well