untangled

NEW CHANNEL: #fulcro
2017-03-08T00:02:03.005595Z

Is there any reason why client.data-fetch/load-data doesn't allow post-mutation-params?

tony.kay 2017-03-08T01:22:02.005596Z

@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.

tony.kay 2017-03-08T01:23:14.005597Z

Let me say that in a different way: load should do everything you need. load-data has a different, but more finicky API.

tony.kay 2017-03-08T01:24:18.005598Z

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

tony.kay 2017-03-08T01:26:11.005600Z

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.

tony.kay 2017-03-08T01:26:39.005601Z

but I don't like changing API out from under people, so instead of "fixing" load-data and breaking people, I added load

urbank 2017-03-08T14:28:18.005602Z

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?

urbank 2017-03-08T14:28:55.005603Z

I hope it's somewhat clear what I'm asking, perhaps I have to refine the question, but I've gotta run

gardnervickers 2017-03-08T15:36:46.005604Z

You compose initial-state calls up the component tree just like om/get-query

tony.kay 2017-03-08T16:25:26.005605Z

@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.

tony.kay 2017-03-08T16:26:10.005606Z

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.

sihingkk 2017-03-08T16:27:32.005608Z

hey, is it possible to have hot-reloading with changing initial-state? I have to reload app every time

tony.kay 2017-03-08T16:27:33.005609Z

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

tony.kay 2017-03-08T16:28:26.005610Z

@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"

tony.kay 2017-03-08T16:28:52.005611Z

but the point of hot-code reload is to be able to work on something without your app reverting to original state 😜

sihingkk 2017-03-08T16:28:53.005612Z

@tony.kay right, thanks

tony.kay 2017-03-08T16:29:13.005614Z

i.e. working on tuning up a dialog, you wouldn't want the app hot-code reloading back to login

tony.kay 2017-03-08T16:29:37.005615Z

So, you'd be better off with a REPL function you could call...but then why not just hit reload πŸ™‚

tony.kay 2017-03-08T16:30:08.005616Z

I guess might get a few secs of speed.

tony.kay 2017-03-08T16:30:28.005617Z

It would also be interesting to do a "targeted" re-init of state...e.g. just some subtree

tony.kay 2017-03-08T16:30:55.005618Z

you could use merge-state! for that

tony.kay 2017-03-08T16:32:34.005620Z

(merge-state! @app Component (get-initial-state Component {})) should work

wildermuthn 2017-03-08T16:49:28.005622Z

Hi there. Thanks for the work on Untangled! I have a question about IQueryParams. Is this disabled in the client?

wildermuthn 2017-03-08T16:50:33.005623Z

Since there’s, as far as I understand it, no reader that we define in the client?

tony.kay 2017-03-08T16:53:41.005624Z

IQueryParams works fine...it's just that you cannot currently plug into the parser to do anything about property params.

tony.kay 2017-03-08T16:53:45.005625Z

on the client at least

tony.kay 2017-03-08T16:54:17.005626Z

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.

tony.kay 2017-03-08T16:54:29.005627Z

I made a big comment about this a few days ago...you might look in archives.

wildermuthn 2017-03-08T16:54:38.005628Z

ok sure

tony.kay 2017-03-08T16:54:53.005629Z

https://clojurians-log.clojureverse.org/untangled/

tony.kay 2017-03-08T16:55:38.005630Z

actually: https://clojurians-log.clojureverse.org/untangled/2017-03-06.html

tony.kay 2017-03-08T16:55:50.005631Z

around time 18:27:53

urbank 2017-03-08T18:29:07.005633Z

@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.

urbank 2017-03-08T18:32:46.005634Z

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

tony.kay 2017-03-08T19:02:52.005635Z

@urbank It is true that if you have a global concern that you have to have global logic.

tony.kay 2017-03-08T19:04:59.005636Z

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)

tony.kay 2017-03-08T19:05:35.005637Z

Note in (2) that you now have the out-of-sync problem again

tony.kay 2017-03-08T19:06:09.005638Z

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.

tony.kay 2017-03-08T19:06:33.005639Z

Your cache invalidation, normally a hard problem, is now simpler: when you load, update views

tony.kay 2017-03-08T19:07:09.005640Z

You also don't have arbitrary costs on UI queries (e.g. large computations on each render frame)...only when the data actually changes

urbank 2017-03-08T19:15:40.005641Z

@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.

tony.kay 2017-03-08T19:17:44.005642Z

@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

tony.kay 2017-03-08T19:18:01.005643Z

and you've added computational overhead into the rendering itself

tony.kay 2017-03-08T19:19:32.005644Z

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.

tony.kay 2017-03-08T19:19:52.005645Z

this is part of the UI refresh internals

tony.kay 2017-03-08T19:20:15.005646Z

So, if you pull in an entire table, it is kind of a hack

tony.kay 2017-03-08T19:20:43.005647Z

and you lose the "local reasoning" and refresh model to some extent

tony.kay 2017-03-08T19:20:54.005648Z

so I probably shouldn't be telling ppl they can do it 😜

urbank 2017-03-08T19:24:48.005649Z

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.

tony.kay 2017-03-08T19:25:25.005650Z

Right. The graph is about two things: Your UI structure and entity relations.

tony.kay 2017-03-08T19:26:02.005651Z

entity relations are more constrained (evolve in sync with server and the schema there)

tony.kay 2017-03-08T19:26:14.005652Z

UI structure is up to your specific app usage of that data

tony.kay 2017-03-08T19:27:04.005653Z

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

tony.kay 2017-03-08T19:27:37.005654Z

The ui-specific ones have a UI-specific lifecycle.

tony.kay 2017-03-08T19:27:50.005655Z

and that has to be coded somewhere/somehow.

tony.kay 2017-03-08T19:28:02.005656Z

kind of distilling it down to base concepts.

tony.kay 2017-03-08T19:28:44.005657Z

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

tony.kay 2017-03-08T19:29:46.005660Z

So now you entire application boils down to app-state -> app-state -> app-state (over time)

tony.kay 2017-03-08T19:30:59.005661Z

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.

tony.kay 2017-03-08T19:32:29.005664Z

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

tony.kay 2017-03-08T19:33:06.005665Z

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.

tony.kay 2017-03-08T19:35:10.005666Z

not sure it is doable, but would be a nice add

urbank 2017-03-08T19:36:39.005667Z

Thanks, that cleared up a lot of things for me

tony.kay 2017-03-08T20:43:09.005668Z

@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

wildermuthn 2017-03-08T20:45:29.005669Z

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?

tony.kay 2017-03-08T20:46:08.005670Z

So you're coming from stock Om Next

tony.kay 2017-03-08T20:47:08.005671Z

set-query! does technically do that under the covers via gather-sends, but Untangled doesn't model remote loads how (stock) Om Next does.

tony.kay 2017-03-08T20:47:23.005673Z

because then you have to write a parser for the whole mess

wildermuthn 2017-03-08T20:48:31.005674Z

Understandable. I think the right path is to use the :ui states as params?

tony.kay 2017-03-08T20:48:51.005675Z

?

tony.kay 2017-03-08T20:49:08.005676Z

:ui/... keywords are automatically eliminated from queries

tony.kay 2017-03-08T20:49:11.005677Z

when remoting them

tony.kay 2017-03-08T20:49:14.005678Z

as a convenience

tony.kay 2017-03-08T20:49:22.005679Z

no need to see ui-only query portions to the server

tony.kay 2017-03-08T20:49:41.005680Z

nothing at all to do with params

tony.kay 2017-03-08T20:51:03.005681Z

So, it seems you have several things going on in your head at the moment...we could tease them apart πŸ™‚

wildermuthn 2017-03-08T20:51:45.005682Z

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

tony.kay 2017-03-08T20:52:05.005683Z

In general, set-query! will have nothing to do with loads in Untangled. You could use it to change the query dynamically.

tony.kay 2017-03-08T20:52:09.005684Z

Ah, ok, specific problem

tony.kay 2017-03-08T20:52:47.005685Z

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).

wildermuthn 2017-03-08T20:53:06.005688Z

Ok, that makes sense.

tony.kay 2017-03-08T20:53:28.005689Z

then use a post-mutation on the load if you needed to "fix up" the UI graph to display the results

wildermuthn 2017-03-08T20:53:29.005690Z

Behind the scenes these are mutations that get passed to the server as load events?

wildermuthn 2017-03-08T20:54:38.005691Z

I'll dig into the docs and videos more. Most of my questions are uninformed. :) Great videos, btw!

tony.kay 2017-03-08T20:55:31.005692Z

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).

tony.kay 2017-03-08T20:56:07.005693Z

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.

tony.kay 2017-03-08T20:56:24.005694Z

So, load is a mutation that adds something to this internal app-state queue.

tony.kay 2017-03-08T20:56:57.005695Z

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.

tony.kay 2017-03-08T20:57:14.005696Z

in a nutshell

tony.kay 2017-03-08T20:58:16.005697Z

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!)

tony.kay 2017-03-08T20:59:43.005698Z

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.