Fork me on GitHub
#fulcro
<
2022-11-17
>
cjsauer00:11:05

What is the best way to perform derived queries? Meaning, I have :some-edge that doesn’t exist in the db, but can be calculated from keys that do, and I’d like it to be included in the ui tree. My first thought was just to perform the calculation directly in the render function with use-memo, which would mean using link queries to obtain the needed db tables. This means the results wouldn’t be shared between components that query for the same entity, which in my specific case shouldn’t be a problem. Is there a better way? My other idea was to use a local pathom remote, but that’d require that I maintain that edge manually by triggering loads at the appropriate time…

dvingo00:11:36

You can try using subscriptions https://github.com/matterandvoid-space/subscriptions sample app here: https://github.com/matterandvoid-space/todomvc-fulcro-subscriptions They are exactly designed to deal with derived data, use-cases such as what you described. I'm not sure I follow what you mean that results won't be shared between components, but subscriptions are reused across all components so that will solve that problem

Björn Ebbinghaus12:11:35

The client db is just an ordinary clojure atom, so you could https://clojuredocs.org/clojure.core/add-watch.

cjsauer14:11:49

Leveraging reframe is an interesting approach...I did find https://github.com/awkay/fulcro-with-reframe and the accompanying https://www.youtube.com/watch?v=ng-wxe0PBEg. Tony mentions it would preclude some fulcro features, like dynamic routing, but perhaps there are reframe packages that can solve those missing pieces. @U051V5LLP is this the repo you started your work from?

cjsauer15:11:30

@U4VT24ZM3 I think the issue with watching the state atom is that it's too fine-grained. It gets swapped on frequently with changes that wouldn't affect the derived query, which would end up creating lots of unnecessary work without some form of optimization involved.

dvingo15:11:39

Nope, it's a completely separate repo. There is no re-frame code in there. The subscriptions library was ported from re-frame but now includes lots of additions not in re-frame (subscribing directly to functions, EQL query support). The simplest way to use the library is with plain react dom function components like this example: https://github.com/matterandvoid-space/todomvc-fulcro-subscriptions/blob/766d27be316c3f2ab6a23bd8db30932ec0601a4f/src/main/space/matterandvoid/todomvc/client/ui/main_page.cljs#L32 you can combine using these in an existing fulcro app

cjsauer16:11:05

Interesting. I think this is a bit too far off the stock fulcro path for my needs at this moment. I'd like to see what I can drum up with the defaults first before I reach for something more experimental. Specifically I think state machines might work well for me here. I appreciate the link, and the readme was a good read. It definitely has me thinking.

dvingo16:11:10

For sure! I think both styles for dealing with derived state work well (pull from UI, vs compute after mutate) I think it comes down to preferences. Fulcro is so well designed that we can choose how we architect around it 😎

fulcro 1
Jakub Holý (HolyJak)18:11:02

A common solution is to simply compute the derived data in render, as you proposed. Even though it seems inefficient, it works most of the time. Alternatives depend on how the data the computation depends on get in. If they do only in few places, few simple ways, you could handle that by having dedicated mutation(s) / post-mutation to manually trigger recomputations of the derived data.

cjsauer00:11:47

I should mention that this is purely client side. I have no server.

cjsauer15:11:25

@tony.kay did you ever take the idea introduced in https://www.youtube.com/watch?v=78yzKPWikro any further? It doesn't appear that the :tx-hook config option ever made it to release, but the concept behind it seems very useful.

dvingo15:11:23

I was experimenting with using fulcro rendering with subscriptions and that work led to adding this hook: https://github.com/fulcrologic/fulcro/commit/df511f2ab7684e95edd0aa1397f43a99492d8d8a The idea is that after mutations run that hook gives you a place to mutate the state atom again to compute derived data, inserting it back into the state atom, and then having the component run. The components will add queries that look for the derived state, so the normal fulcro UI flow still applies (query for everything, whatever you query for lives in app-db) I was going down that route here: https://github.com/matterandvoid-space/subscriptions/blob/686912827bae018a594088bc712d454b78fcdb2e/src/main/space/matterandvoid/subscriptions/fulcro_components.cljs#L85 but ultimately found the dev experience of just using helix to render the dom with subscriptions for reactivity and using fulcro for only data manipulation to be a much more enjoyable and understandable developer experience. it's very simple. But that code is there for inspiration or pull requests, if you want to pursue such an idea.

👆 1
🙏 1
😻 1
tony.kay16:11:40

I don’t personally use any kind of derived data mechanism outside of Fulcro. I just haven’t felt the personal need for it, and thus I’ve provided requested hooks whenever asked as a means by which the community can experiment with their own solutions (as @U051V5LLP has said). For me, I put derived data into two categories: 1. Stuff that can be easily computed in the UI. Write a function. Query core data, Call it in UI. Easy to understand, almost always plenty fast. (premature optimization leading to feature bloat) 2. Write the derivation as part of the operation of the component, which is implemented in a UISM (e.g. RAD reports do this heavily). For example RAD reports load the raw report data, then trigger a sequence of things (data transforms, pivots, sorting, filtering). The logic is very easy to follow because it is all in one computational unit that has to do with the thing in question. All updates to that data model go through the state machine, so it is trivial to keep all the derived data correct. (insufficient consideration of the complexity of the real problem might mean derivation system is actually the wrong solution) In my experience what most people want from derived data systems is (1). If they really want (2), then the cascade of things gets too confusing with subscriptions and I/O and derived data systems are also a poor fit. In my experience ppl worry WAY too much that calling a function in render (i.e. (1)) is going to be too slow. I’ve been writing apps with Fulcro for 5 years now. That has not been my experience, and even in cases where an optimization was needed, the rare optimization was not a huge burden that made me want a derivation system in my stack. I’ve not tried @U051V5LLP approach, but I can see the basic appeal.

tony.kay16:11:23

So, while the “feature hound” in me kind of wants some kind of data derivation mechanism I just haven’t felt a pain that tips the scales in favor of one.

👍 1
tony.kay16:11:41

The video vs current implementation reflects that dual feeling: show you how you can integrate such a thing because I can imagine it being fun/useful, but not actually feeling the need for it myself.

cjsauer16:11:46

Hm...great points, and they align with my own thinking. The weight of bringing in a signal graph was making me feel uneasy given that my problem doesn't seem to require such a large paradigm shift. A state machine might actually be perfect here. Even without the derived state desire, the behavior surrounding this component is starting to become unwieldy, and formalizing it into a state machine is a great next step.

cjsauer16:11:15

If that turns out to not be enough, I may look into the :before-render hook (thank you for pointing me to that @U051V5LLP).

👍 1
hmaurer15:11:55

@U6GFE9HS7 out of curiosity, what data do you need to derive?

cjsauer19:11:46

@U5ZAJ15P0 I'm building a node editor, and the contents of a node are driven by how its currently connected in the graph. So the derivation requires a lot of context outside of the node's own props.

hmaurer21:11:56

@U6GFE9HS7 and i take it you have already explored representations of the graph that would make deriving the data for each node as quick as possible?

cjsauer13:11:27

@U5ZAJ15P0 I haven’t run into a performance issue yet, and the derivation is quick. I’m now experimenting with putting the derivation into the parent component’s render function, where I have most of the necessary context. While not the most optimal approach it does benefit from being very simple. I’ll need to experiment with how it performs on slower devices.

hmaurer21:11:58

@U6GFE9HS7 you could also memoize the derivation to speed up any future renders

cjsauer21:11:46

Yea was considering using the use-memo hook for that. Thing is the ui tree is recreated from root on each render right? So the memoized value comparison turns into a deep equals each render anyway…would need to measure.