This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-09-26
Channels
- # aleph (3)
- # announcements (6)
- # babashka (14)
- # beginners (8)
- # biff (16)
- # calva (4)
- # cider (7)
- # clj-kondo (8)
- # cljs-dev (26)
- # clojure (65)
- # clojure-austin (2)
- # clojure-brasil (1)
- # clojure-europe (35)
- # clojure-nl (4)
- # clojure-norway (45)
- # clojure-portugal (1)
- # clojure-uk (9)
- # clr (5)
- # community-development (6)
- # conjure (1)
- # cursive (3)
- # events (8)
- # fulcro (1)
- # honeysql (19)
- # hyperfiddle (31)
- # introduce-yourself (1)
- # lsp (7)
- # matcher-combinators (10)
- # off-topic (17)
- # practicalli (1)
- # ring (30)
- # shadow-cljs (6)
- # testing (2)
> https://cjohansen.no/stateless-data-driven-uis/
This sounds great in theory, and I used to drink this particular flavor of Kool-aid, but I have actually come to dislike it over the years. These are essentially the same patterns you see in React/Redux apps. I find that problems arise apps due to the implicit complexity of keeping 2 separate stores: one server-side database, and a second client-side in-memory atom/redux store. The client-side store is usually a simple projection of the server-side database, and that's where complexity can creep in.
For example, you fetch a list of objects from the server and store them in the client-side store. You hook up a bunch of UI components to listen to that slice of the store, then have a button that you expect to influence that collection (think about a remove
or add
button). Now you want to issue a request to the server to mutate the collection, but you also need to mutate the client-side store to reflect the same change, so you essentially reimplement the backend functionality (remove/add are simple, but often the effects are more complex) in the frontend. If the backend mutations return errors, you also have to handle those and make sure the frontend store matches the state of the backend given that specific error (maybe the state changed, maybe it didn't. Maybe some other part of the state changed). As applications grow, I tend to see these frontend stores explode in size and complexity.
The problem gets worse at even greater application size, because you will often have a new feature that also needs to listen to some part of the application state, but due to decisions in the implementation of 1 part of the UI, or programmer ignorance, a second "view" of the same backend data is used. E.g. there was an original users
array in the store, but that powers some legacy page/feature, and whenever the value changes some effect is triggered that I don't actually want anymore, so now I need newUsers
in the store. Both users
and newUsers
represent the same type User[]
, and are ostensibly meant to reflect the same data, but they do not. This also applies to any derived data, which is even more common (e.g. there are users
in the store, but someone else needs userAddresses
. Both should always be in sync, but fetching the addresses is a separate step. It's so easy to get users
and userAddresses
out of sync, and any mutations of users
should also affect userAddresses
. You eventually see that you are reimplementing your entire database on the client).
These days I prefer to forego the frontend store approach entirely and rely on simpler caching mechanisms. In React this means using react-query
or swr
for REST APIs or apollo
for graphQL apis. They really simplify this whole thing tremendously. In Clojure/script I don't know exactly what this would look like, but you can get really far with old-school server-side rendered apps (especially if you also consider using https://htmx.org/).