This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-12-12
Channels
- # adventofcode (42)
- # aleph (10)
- # announcements (1)
- # asami (138)
- # babashka (7)
- # beginners (7)
- # biff (13)
- # cider (7)
- # clj-kondo (15)
- # clojure (53)
- # clojure-austin (11)
- # clojure-belgium (2)
- # clojure-europe (23)
- # clojure-nl (1)
- # clojure-norway (55)
- # clojure-sweden (5)
- # clojure-uk (4)
- # cryogen (7)
- # cursive (63)
- # datomic (5)
- # eastwood (6)
- # emacs (31)
- # fulcro (7)
- # hyperfiddle (9)
- # introduce-yourself (3)
- # java (11)
- # lsp (10)
- # malli (14)
- # membrane (35)
- # off-topic (13)
- # portal (12)
- # prelude (1)
- # releases (2)
- # ring-swagger (27)
- # shadow-cljs (8)
- # timbre (25)
do you have any good sources on function UI framework design/motivation? I've used React in anger but a long time ago, before Hooks even existed, I don't know the status quo but feel like I should understand React better (maybe), since it has at least some "solution" to many possible problems, although probably complected with some DOM design constraints
feel like something summarizing high level react architecture and history from an FP perspective would be the dream but dunno if that exists or anyone knows of it
motivaitonis fucking around with side effects using Membrane but without using the membrane.components ns but my own (similar) patterns
have an asynchronous (pure) calculation as part of a render so need some pattern to deal with that, can think of a few but which to choose
eg. what we discussed a while ago in terms of (passing membrane.component defui elements around first class) looks a lot like what I'm now reading react calling "render props"
and in fact the caveat in their docs that passing components~functions around complicates comparison using data for (caching/avoiding recalc) is exactly a problem that I hit
it's also hard because actually a lot of React docs and explainers are a lot to do with (compensating for) specifics of js too, which I also have known but complicate everything
I think Elm is probably the most functional UI library that I'm aware of, https://guide.elm-lang.org/architecture/.
Although, they kind of punt on how to compose UIs with more complicated examples.
> eg. what we discussed a while ago in terms of (passing membrane.component defui elements around first class) looks a lot like what I'm now reading react calling "render props" > and in fact the caveat in their docs that passing components~functions around complicates comparison using data for (caching/avoiding recalc) is exactly a problem that I hit Maybe a better place to start is a description of some of the problems you're working on or running into.
the thing which made me start to reach for something more complicated than (like membrane.components but manually passing paths around) is that I have a render calculation which is asynchronous (a promise in an external library)
which means I have to do /something/ I'm not doing now, I could do a dispatch on render but that seems problematic, I could have a different kind of dispatch that I pass for use in-render which I can turn on and off, I could do something like return {:rendered normal-component-partially-calculated :waiting (async thunk for our calc} but now I've changed the type signature for all my components everywhere and have to change rendering to be async-aware
reading react docs it seem like they would maybe use a hook to fire an async event modifying the backing state on render, which still doesn't seem like the nicest idea
hooks in general I might not be understanding, all your state declarations are now coupled to the order of components being rendered, but how can that interact with what react calls HoC or conditional renderProps, aren't you now locked into calling the same render function forever
I've thought a bit about how I think render triggered events should look like: https://phronmophobic.github.io/membrane/membrane-topics.html#Effects-on-Mounting-or-Unmounting
I think there's still more work to be done here.
One of the pitfalls that should be avoided is coupling when and how rendering happens with the main render thread.
I think life is easier when you don't mix effects with view functions.
But tbf, I don't think there's a lot of good advice for how to accomplish that for things like async rendering (and there's lots of bad advice)
The general ideas are: • the view function just takes data and returns views synchronously. If you the data you need doesn't exist, then either return nothing or a placeholder • separately, the application can decide if more data needs to be fetched or calculated in the background. The timing of this check can be decided based on the use case (ie. check very state change, check after each render, check periodically, etc). • It's ok to take your time constructing or prerendering a view in the background and then just shoving the result into the application state that the view function can present when it's ready. (This also applies to building an optimized event function).
This is more or less the strategy I used for my treemap demo, https://github.com/phronmophobic/treemap-clj/blob/f4f39c6a9e8799a7901d4429417fb74f0cf1ea43/src/treemap_clj/webgl.cljs#L108. https://blog.phronemophobic.com/treemap/treemap-demo.html
Rendering the treemap and building an rtree for events can take a moment, but you can do all the hard work in the background and cache the result.
One thing is that in cljs the add-watch strategy obv won't benefit from a thread, also I forget the add-watch order of execution guarantees in clj but iirc they're not guaranteed to return in the same order as the state is updated
The "present" pattern you present is interesting, easy to add traversing protocols like this using the -children protocol right. As it's written I believe it would only trigger for the components which are mounted immediately and but any which become mounted due to state changes
> One thing is that in cljs the add-watch strategy obv won't benefit from a thread, also I forget the add-watch order of execution guarantees in clj but iirc they're not guaranteed to return in the same order as the state is updated Yea, the current docs are more of a rough sketch than a real plan. I'm pretty sure this is the right direction, but not 100% sure what the exact best practices might be. It would be nice to have some experience reports and then package the best options in to easy to reuse pieces.
> The "present" pattern you present is interesting, easy to add traversing protocols like this using the -children protocol right. As it's written I believe it would only trigger for the components which are mounted immediately and but any which become mounted due to state changes Right. There are some interesting design considerations. You might a container component that requires an async step to load. Its child might also require async data to be loaded and so forth. There are multiple strategies for loading these types of UIs and waiting for them to stabilize. I think a lot of UI libraries focus on one specific instance of the problem and couple the implementation in a way that makes choosing another strategy very awkward. They also tend end up an OO solution to satisfy the underlying OO system (ie. html, swing, javafx, etc) instead of applying normal functional programming.
I think I agree that there are multiple "similar looking" but actually different-really possibilities for these relationships between render and different types of async dependency/triggering
another thought I had is that in the mounting example you give, you use vary-meta to attach a protocol closed over a function, there's something I don't like about that - that attached function (which could be a record==data) only exists now in the metadata, but there's a feeling that maybe it should be part of the 'data level' of the result
you could 'assoc' it in, I dunno what that would buy you, if the function was a record or serializable then serialization or data inspection would work better after
if it's a totally opaque fn then kind of don't buy much but you don't know which it could be in general
> another thought I had is that in the mounting example you give, you use vary-meta to attach a protocol closed over a function, there's something I don't like about that - that attached function (which could be a record==data) only exists now in the metadata, but there's a feeling that maybe it should be part of the 'data level' of the result Yea, using protocols via metadata is partly to keep the example code short. I'm not sure if it's the best way to implement it.