Is there any reason why client.data-fetch/load-data doesn't allow post-mutation-params?
@tobias It was the earliest API call for data fetching, and has been superceeded by load
. It can technically do things in a single call that load
cannot, but I was leaning towards deprecating it, so it has not been upgraded with options that load
has.
Let me say that in a different way: load
should do everything you need. load-data
has a different, but more finicky API.
load-field
is a special (and common) case of loading some sub-portion of the UI tree from the perspective of a component that is on-screen. It writes the more complicated load
that you would need
ultimately you're always wanting to load some portion of the graph (as a tree). Technically, load
can accomplish anything you'd want to do from that problem statement. load-field
is a useful specialized case. load-data
was just the first attempt at load
.
but I don't like changing API out from under people, so instead of "fixing" load-data
and breaking people, I added load
How do you handle a case where the initial state of some component is derived from another component. So for example: the todo list component's initialState adds a list of todos to the database. Now we want to add a component with an initialState that is a function of the TodoList's initial state. How would this be done?
I hope it's somewhat clear what I'm asking, perhaps I have to refine the question, but I've gotta run
You compose initial-state
calls up the component tree just like om/get-query
@urbank Perhaps you mean: how do I make some component elsewhere in the tree depend on the state in another, where the component might not be on the same branch? Well, often you just query for that other component's data and initialize the component as normal (e.g. if you want the todo list item count anywhere in the app, query for the whole table and count the entries in the UI code: [ [:todo-items/by-id '_] ]
. That way your data is still "dry". Another thing you might like to do is show some sub-list, where you'd perhaps like to pre-sort/filter into a "view" of data elsewhere in the app. In that case, do the transform in initial-state
of the secondary component, and just use initial-state
of the primary to get the data to work on:
(defui MySortedSublist
static uc/InitialAppState
(initial-state [cls params] {:my-items (filter-and-sort (get-initial-state TodoList))})
etc.what @gardnervickers said is always true: you have to compose children initial state into parents...but any other kind of normal information composition is up to you and is otherwise unconstrained.
hey, is it possible to have hot-reloading with changing initial-state? I have to reload app every time
To preempt the question "why get-initial-state
instead of initial-state
?": Server-side rendering is done in Java VM, and initial-state
won't be available there. get-initial-state
is how you call the static
protocol method on both client/server
@sihingkk Not built in. but you could have figwheel trigger a function that called your initial state on root and installs it as the "new app state"
but the point of hot-code reload is to be able to work on something without your app reverting to original state π
@tony.kay right, thanks
i.e. working on tuning up a dialog, you wouldn't want the app hot-code reloading back to login
So, you'd be better off with a REPL function you could call...but then why not just hit reload π
I guess might get a few secs of speed.
It would also be interesting to do a "targeted" re-init of state...e.g. just some subtree
you could use merge-state!
for that
(merge-state! @app Component (get-initial-state Component {}))
should work
Hi there. Thanks for the work on Untangled! I have a question about IQueryParams. Is this disabled in the client?
Since thereβs, as far as I understand it, no reader that we define in the client?
IQueryParams works fine...it's just that you cannot currently plug into the parser to do anything about property params.
on the client at least
So, you could use dynamic queries to change the query, but the model in untangled is not to use property/join params on the client side.
I made a big comment about this a few days ago...you might look in archives.
ok sure
actually: https://clojurians-log.clojureverse.org/untangled/2017-03-06.html
around time 18:27:53
@tony.kay Thanks! Although it seems that with the composition of the initial state the MySortedSubList component needs to be manually updated if a mutation changes the TodoList's data.
So it wouldn't stay in sync. I imagine that could get a bit hairy if there were lots of components like MySortedSubList - finding them and keeping them all consistent
@urbank It is true that if you have a global concern that you have to have global logic.
This is a trade-off between stock Om Next and Untangled. In Om Next you can "customize" the query for the sublist, and then write a parser emitter that "does the right thing". This fixes the sync issue. However, it also means that now: 1. you have to write/maintain parser emitters for each component. That is less composable since your parser is tied to your UI structure (e.g. reusable components get harder) 2. You run that logic on each re-render (possibly getting quite slow on frame refresh, which leads to caching and memoization code you have to add)
Note in (2) that you now have the out-of-sync problem again
so, Untangled takes the approach: Make a global mutation that knows how to update derived views when tables change in some global way. Trigger those as post mutations on loads.
Your cache invalidation, normally a hard problem, is now simpler: when you load, update views
You also don't have arbitrary costs on UI queries (e.g. large computations on each render frame)...only when the data actually changes
@tony.kay Yeah, that makes sense. I can definitely see the upside. So essentially a lot of the heavy lifting is done in mutations. Still in the process of absorbing the philosophy of untangled. I suppose you could still always query the tables from the root of app-db, and let components transform the data on the fly... though that might not always be the best idea.
@urbank the problem with the link query on a table is you lose the metadata, and should then prob not render it with a component
and you've added computational overhead into the rendering itself
This is kind of a technicality, but if you render a component that has a query, the data that is passed to the component gets "marked up" via the query. In general the rule is: If you render a stateful component, you must pass it data that came from the query of that component.
this is part of the UI refresh internals
So, if you pull in an entire table, it is kind of a hack
and you lose the "local reasoning" and refresh model to some extent
so I probably shouldn't be telling ppl they can do it π
Right, so the table is for keeping the specifics of 'entities' in once place, whereas the structure that components query should be elsewhere in the db, built from references to the tables.
Right. The graph is about two things: Your UI structure and entity relations.
entity relations are more constrained (evolve in sync with server and the schema there)
UI structure is up to your specific app usage of that data
You could think of the graph being colored: one color for the "server-based schema" and one for UI-specific concerns. Perhaps another where there is overlap
The ui-specific ones have a UI-specific lifecycle.
and that has to be coded somewhere/somehow.
kind of distilling it down to base concepts.
MVC -> It goes in custom UI models and controllers Om Next -> It can go in the parser emitters Om Next/Untangled -> Represent it as information in the graph with pure functions that move you from one graph state to the next
So now you entire application boils down to app-state -> app-state -> app-state (over time)
whereas in the parser emitter case, you do have app-state -> app-state
as more of a pure model concern, but there is also an app-state -> ui
transform at each step for render.
so another way to say it is that we eliminate the app-state -> ui
transform because we saw the graph as an easier thing to maintain and reason about
I have an Untangled feature issue on client where I do plan to add in parser composition support, so you could do the app-state->ui
step if you want.
not sure it is doable, but would be a nice add
Thanks, that cleared up a lot of things for me
@wildermuthn Actually time stamp 21:32:38 is a better spot. Technically you could use query params to change the query...more clarity there, hopefully, Feel free to ask more questions
Thanks! set-query! works, but doesn't automaticall requery the remote in my setup. Looks like I need to call one of the load fns?
So you're coming from stock Om Next
set-query!
does technically do that under the covers via gather-sends
, but Untangled doesn't model remote loads how (stock) Om Next does.
because then you have to write a parser for the whole mess
Understandable. I think the right path is to use the :ui states as params?
?
:ui/... keywords are automatically eliminated from queries
when remoting them
as a convenience
no need to see ui-only query portions to the server
nothing at all to do with params
So, it seems you have several things going on in your head at the moment...we could tease them apart π
Still working my way through the Untangled docs. Just thinking on how to get the same kind of functionality that we see in Om Next's wiki example for the autocomplete
In general, set-query!
will have nothing to do with loads in Untangled. You could use it to change the query dynamically.
Ah, ok, specific problem
Yes, on a debounced UI event (e.g. field is changing) you'd trigger a load
(which you can embed in a mutation via load-action
).
Ok, that makes sense.
then use a post-mutation
on the load if you needed to "fix up" the UI graph to display the results
Behind the scenes these are mutations that get passed to the server as load events?
I'll dig into the docs and videos more. Most of my questions are uninformed. :) Great videos, btw!
Here's the realization about loading on-the-fly: In Om Next, to get the UI experience you want, you're always doing this:
1. Mutate the state to put a marker in that your parser can read to trigger loads
2. Write the parser bits to see that marker
3. Make sure you somehow overwrite that marker to say you're done so you don't accidently re-trigger the remote interaction
So, in Om Next remote reads are always some kind of mutation (e.g. even set-query!
is really setting something that causes gather sends).
So, with this realization, we decided: why not make a "global spot" for the load markers we want, that we can manage as data? Get the parser out of the business.
So, load
is a mutation that adds something to this internal app-state queue.
and it is a "remote" mutation, but when our network stack sees a load
mutation, instead of sending the load, it pulls the read items off the queue and sends those instead.
in a nutshell
Once you get to the network layer there is no diff between mutations and reads. It is all just query notation, and the merge behavior is the same for both (call the callback you're given, which is just merge!
)
Glad you like the videos. Sometimes when coming from raw Om Next it can help to see what we've decided to do. Technically Untangled is Om Next, it's just one possible way of setting it up.