Fork me on GitHub
#om
<
2015-10-18
>
scriptor01:10:10

how relevant is the euroclojure talk on om.next, have there been many significant design changes?

abp01:10:20

there is a newer talk recorded at the lisp nyc user group about two weeks ago

scriptor01:10:25

@abp: I was there for that one! Just looking to see what other resources I can study

abp01:10:10

@scriptor: oh wow nvm then

dnolen02:10:25

@scriptor: mutations weren’t sorted out until a couple of weeks ago

dnolen02:10:55

that was the last piece of the big puzzle, there are some other finer details still but nothing major

scriptor03:10:12

@dnolen: ah, makes sense, thanks!

tony.kay05:10:11

Newbie question…I’ve been playing with om-next and am seeing apply being used (for example in the Person list tutorial) to render lists. I understand what apply does, and in the cases it is being used I see that it is splicing lists in as parameters; however, I noticed that removing the apply works just fine (as if something underneath is unrolling the collections already). So, why are we still using apply? Does something break in pathological cases? It sure seems like unnecessary noise in the examples.

tony.kay07:10:27

OK, after hours of sorting through code, I’m guessing that React improvements are the reason. I found this commit from last Oct in React: https://github.com/facebook/react/commit/c07ea0ba34df57fc54b218d6b4edcd090ed6411c which talks about making React automatically recursively traverse nested collections of children if they support ES6 iteration. Since Clojurescript’s data structures implement ES6 iteration, I’m guessing we are still using apply to prevent browser compatibility breakage?

thheller08:10:25

@tony.kay: oh nice did not know that. pretty cool, seems to work with for too.

thheller09:10:31

@tony.kay: one issue that comes to mind though is to when is traverseAllChildren called? might break with regards to some lazy seq semantics and things not being in render anymore.

anmonteiro11:10:44

@dvcrn: now I get what you're trying to do, after seeing the whole thing

anmonteiro11:10:13

however I still don't think it's the intended way; what I'd do is have a map inside a specific :codemirror key and then query for that

anmonteiro11:10:41

then you don't have to fabricate keys in the read methods

dvcrn14:10:56

@anmonteiro: can you give me an example of what you mean?

dvcrn14:10:00

you mean I should put that stuff inside a :codemirror key inside the state atom?

anmonteiro14:10:22

so, modify your app state such that :window is inside :codemirror

anmonteiro14:10:34

like {:codemirror {:window "foo"}}

dvcrn14:10:40

then if another component wants :window too for example, I'll have to put that twice inside the state atom, and my mutators have to edit it twice

anmonteiro14:10:58

so if :window is not specific to CodemirrorComponent I wouldn't wrap it into the :codemirror key when reading it 😛

anmonteiro14:10:16

and if it is, I'd nest it in the app state map

dvcrn14:10:00

in that way I have to make state specific keys for every component and not just have my global state and allow components to filter on that. I think this could get problematic when you start sharing state between components can you maybe explain what you think is wrong with my approach?

sander14:10:45

it seems to be cleanest to structure the app state tree just like the component tree

sander14:10:45

didn't read the full discussion, but i'm struggling with how to structure stuff as well, and this is how i feel about it now simple_smile

dvcrn14:10:24

it makes sense, definitely. Just trying to think how to implement shared state with keeping things DRY

dvcrn14:10:59

the structure part already ate a good amount of time from me 😛 I keep finding a solution, ask about it, get feedback and then do it over again lol

sander14:10:02

@dvcrn: what piece of state are you sharing?

dvcrn14:10:20

for example I have the application (or window) size that multiple components need

sander14:10:33

{:component-1 {:window-size [1920 1080]} :component-2 {:window-size [1920 1080]}}

dvcrn14:10:05

then I need to add that key to every component that needs it, plus the mutator has to be adjusted everytime a new component needs this state. Seems like a lot of copying to me :x But I can see how this is working

anmonteiro14:10:37

I'm also struggling with structuring

anmonteiro14:10:56

I didn't say it's wrong, it's just not how I've seen from dnolen's examples

anmonteiro14:10:29

I'd also say that normalization might solve the issue at hands

anmonteiro14:10:58

however, it seems to me that those are more "helper keys" than something to stick into a component's identity

anmonteiro14:10:27

for me it might even be something I'd put in local state, have you considered this?

anmonteiro14:10:55

e.g. a component that has :window in its local state and other components are nested into that one

sander14:10:36

@dvcrn: adding it to every component can be trivial, (defmethod read :component-1 [env key params] {:value (merge (actual-value env key params) (shared-component-map env))})

dvcrn14:10:25

I think I'll give this way a shot and see how it goes

dvcrn14:10:58

if this is the intended / optimal way, it makes things a lot more clear

dvcrn14:10:05

I'm not there yet, but how would this integrate with a server?

anmonteiro14:10:19

what do you mean?

anmonteiro14:10:23

Om next in general?

anmonteiro14:10:02

might require some changes if you're integrating with e.g. Json over HTTP

anmonteiro14:10:17

that code works for edn, I believe

dvcrn14:10:22

I took only a sneak peek into it but it is transacting the entire state, right? If my state is built like my component tree, does my server has to take care of creating a dataset that looks like my component tree?

dvcrn14:10:29

I should probably read more about it though before asking questions 😛

sander14:10:11

@dvcrn: yes, your endpoint has to take care of that. you can also make a local function that collects data from multiple http endpoints and turns it into one tree

dvcrn14:10:40

that seems not very optimal to me on first look. So now everytime I create a new component, that function (be it server or client) has to know the name of that component so it can return data in that format plus my mutator also needs to know this information. I thought it was more like: A component requests the data it wants (:a, 😛, :c) and the server gives that data back without knowing anything about the component. With this, just changing the name of a component would cascade through the entire application

sander14:10:16

@dvcrn: the data keys don't need to be equal to the component name

dvcrn14:10:10

yeah but if we model the state like the component tree, multiple points still need to know about the presence of a new component. That's what I meant 😇

sander14:10:07

@dvcrn only if the new component requires data from the server that wasn't accessed by the app before

sander14:10:18

then you need to think about where in the data tree to put that new data, and then it often seems possible to use a similar hierarchy to the (new) component hierarchy

dvcrn15:10:26

that makes sense, yeah

dvcrn15:10:48

I think I should try an actual application to get my head around it

sander15:10:04

the code/repl comments without (render) from https://github.com/omcljs/om/wiki/Components%2C-Identity-%26-Normalization seem to help to experiment with different designs

dvcrn15:10:22

yeah I ate my way through that already simple_smile

sander15:10:40

i.e. make a state tree, look at the normalization and denormalization, iterate

dvcrn15:10:26

@sander ok another small question on top of that before I go to sleep: Given I did it the way you guys explained it, what would be the best way to exclude local state and stuff that should not go to the server. For example if I have a :window key, or keys that define which component is currently visible. It of course doesn't make sense to handle these like the rest of the data

sander15:10:33

@dvcrn: i think remote queries are only ran for attributes that aren't already returned by your local read fn

dvcrn15:10:17

for read yes, but how about write / mutate?

dvcrn15:10:37

can I, say, put that stuff into a :local key and tell my fn to exclude all of that?

sander15:10:40

@dvcrn: i think mutations are only done remotely if you set :remote true

dvcrn15:10:55

all right, more stuff to hack on tomorrow simple_smile thanks for the hints!

sander15:10:34

thank you, it's fun trying to understand om.next without the tutorials being all written yet

jannis15:10:48

Can anyone explain to me how the index is built in om.next (`build-index*` in next.cljs)? It appears queries of nested components need to be structured in a particular way for the indexer to recurse into subcomponents. I'm probably doing it wrong because the class-path->query mapping only contains an entry for the root component and when I call om.next/transact! on a subcomponent, this results in a "No queries exist for component path (frontend.app/App frontend.components.Subcomponent) error.

sander15:10:08

@jannis does the subcomponent implement IQuery and is its query referred to in the root component query?

jannis15:10:11

Yes. But perhaps I'm doing it wrong. I'm doing something like (query [_] (concat [:app/foo :app/bar] (om/get-query Subcomponent)) to separate the information the root component is interested in (`:app/foo` and :app/bar) and the information the subcomponent needs.

jannis15:10:19

So the subcomponent might have (query [_] [:app/baz]), resulting in a root query of [:app/foo :app/bar :app/baz].

sander15:10:09

@jannis try making that `[:app/foo :app/bar {:subcomponent ~(om/get-query Subcomponent)}]

jannis15:10:12

I'm trying that now.

jannis16:10:42

Well, build-index* seems to be looking for "joins" in the query. {:subcomponent ...} is treated as a join and it seems to then look for all components that have the same {:subcomponent ...} query and recurses by indexing those - or something like that.

jannis16:10:41

But I haven't managed to get it to work this way.

sander16:10:30

when indexing, the metadata of (om/get-query Subcomponent) will tell that Subcomponent is associated with that join

jannis16:10:35

Also, aren't queries to refer to application state only? :subcomponent does not appear anywhere in the application state, nor is there a read method for it.

sander16:10:12

if you're going to call it :subcomponent, you need to make read handle that indeed

jannis16:10:20

Right. That seems wrong to me.

jannis16:10:07

I am looking for a way to pass up queries from subcomponents to parent components without having to repeat details of the subcomponent queries in the parent. If the only way to do that is via joins that refer to specific parts of the application state then I'm wondering if that's possible at all.

sander16:10:54

@jannis if you want SubComponent to receive :app/baz, RootComponent needs to provide that value to SubComponent through props

sander16:10:48

so either RootComponent knows that SubComponent needs :app/baz and provides that from (om/props this), or RootComponent does what i just described

jannis16:10:38

IMHO, the most intuitive way to include :app/baz in the parent query via (concat [:app/foo] (om/get-query Subcomponent)). I'm not sure why joins need to be maps.

sander16:10:09

in that case, how does ParentComponent know what props to provide to SubComponent?

jannis16:10:43

Well, okay, I'm just passing (om/props this) down to the subcomponent. That's probably very wrong because it will cause Subcomponent updates whenever anything changes.

sander16:10:44

yes, and it doesn't seem to help building/using reusable components

jannis16:10:15

I keep running against a wall trying to understand how subcomponents with queries can be nested in a useful way.

jannis16:10:01

The TodoMVC example is too simple to help me understand. Yes, if I have one container component (todo list) and many children (todo items), then I can say: TodoItem wants properties x y and z, so that's its query and TodoList wants all todo items, so in TodoList, the query is "all todo items but only with the properties specified in the TodoItem query".

jannis16:10:03

But as soon as there are one or two intermediate layers of components, it becomes less trivial. And I don't see a way around a massive mega-query in each parent component, becoming more complicated the closer to the root you look.

sander16:10:57

depends on how well your app breaks down into a hierarchy of components

sander16:10:21

if it's just a flat list of unrelated components, it becomes one (flat) monster query

jannis16:10:13

I assume a good app would be nested quite heavily.

jannis16:10:00

Also, let's assume a subcomponent is interested in two independent things, e.g. [:foo :bar]. How would it's own and a parent component's query look like, without having a query selector like {:subcomponent ...} which is totally UI-specific?

tony.kay16:10:26

@thheller: what things are not in render? It is a pure function that acts as the React render (om-next in case you were talking om). React is doing side-effects and walking these things, so I’m not sure where a lazy seq would not be expanded. At least I cannot think of an example, but I don’t know what you mean by things that are not in render.

sander18:10:53

@jannis: this becomes easier when you have concrete examples simple_smile

thheller18:10:35

@tony.kay: I mean in render with regards to bindings, if the lazy seq is not realized while in that binding (ie. render) this might get lost

tony.kay18:10:37

@thheller: oh, I see…in case react somehow async defers the expansion, and reconciler is no longer bound, etc

thheller18:10:08

well not async defers

tony.kay18:10:17

hm. right…where would async happen?

thheller18:10:54

I imagine react does something like (-> data (render) (diff) (apply-dom))

thheller18:10:16

if traverseAllChildren happens in diff the bindings won't be there

thheller18:10:29

if it happens in render we are all good, did not check when it happens

tony.kay18:10:47

I see. thinking about what I saw when I read the code...

tony.kay18:10:30

yeah, I don’t think I hit that part of the process.

thheller18:10:34

probably worth to investigate

tony.kay18:10:34

from an optimization standpoint, react does throw out all subtrees if the type of the root is wrong, so I could see it wanting to defer

tony.kay18:10:11

I’m going to lead towards leaving apply out for now unless anyone can tell me why we still need it. If a case comes up that doesn’t work during testing, it is easy enough to add it back in.

thheller19:10:47

@tony.kay: funny twist ... the dev version of react realizes the lazy seq immediately. the prod version does not. so lazy seqs are out.

tony.kay19:10:10

AH….great researching

thheller19:10:01

ReactElementValidator realizes the seq to check if everything is ok, the prod version does not use this and realizes the lazy seq "later"

thheller19:10:17

have not exactly figured out when yet but definitely after render

thheller19:10:17

well doall still makes for nicer syntax than apply

tony.kay19:10:04

or even mapv

tony.kay19:10:19

@thheller: would you mind me running my understanding of om-next query processing by you. I’m trying to make sure I’ve got it

thheller19:10:41

@tony.kay: might not actually be a problem since the binding is around ReactDOM.render and not in an indiviual render. still probably better to stay away from not forcing lazy seqs

thheller19:10:09

@tony.kay: I'm not quite sure I fully get them yet but I can try simple_smile

tony.kay19:10:53

well, perhaps we can grok them together. I get that the root component composes all of the children queries (via looking them up) into one big large thing…it looks like then the parser is called to convert that query into the overall app state. That sound right so far?

tony.kay19:10:33

Any child can compose from its children, making the whole thing recursive at start

tony.kay19:10:07

also preserves local reasoning…I only have to compose my direct children queries into mine

thheller19:10:13

well to be honest I have not quite figured out how/when the query processing happens

thheller19:10:09

the whole remote thing is still a mystery to me

tony.kay19:10:26

Yeah, I’m struggling with that bit as well…You can modify the query dynamically, so I know it can re-happen…also on query parameter changes

tony.kay19:10:53

but it looks like the parser (router) is only ever called on the root component’s query (which makes sense, I guess)

thheller19:10:01

well yeah but I ran into issues where the query is affected by the data it receives

thheller19:10:55

@tony.kay: yes only the root executes the query

thheller19:10:17

and if you don't pass exactly that data down the tree on render, you run into issues.

tony.kay19:10:21

As you descend the rendering tree, it requires that you’ve followed the rules (e.g. you end up with a recursively nested map). and it seems you pass the explicit data via props

tony.kay19:10:57

David did say “the parent filters the state of its children"

thheller19:10:29

the big question for me right now is how "dynamic queries" work. David is writing a tutorial for those.

tony.kay19:10:51

I’m reading over factory code….still missing something about what happens in the wrapped render

thheller19:10:11

wrapped render is here

thheller19:10:51

oh and more bindings. so lazy seq is definitely out

tony.kay19:10:53

I was more looking at the details in reshape-map

tony.kay19:10:06

where the actual magic plumbing occur

thheller19:10:55

yeah the render is part of that reshape-map 😉

tony.kay19:10:41

ok, so factory gives us a function that takes explicit props, and puts them on :omcljs$value

tony.kay19:10:26

path is tracked through metadata the the parser adds while parsing

tony.kay19:10:39

(which is why we cannot break the structure rules…the metadata won’t match)

tony.kay19:10:55

and that is tacked in by the factory as well

thheller19:10:00

basically all components that implement IQuery must receive the query result as their only props

tony.kay19:10:34

that seems to be the case….so I guess you just explicitly pass it through any interstitial components in the UI

thheller19:10:53

yeah but I'm a bit skeptical. with old Om you passed cursors arround, which looked like data but weren't

thheller19:10:30

om.next you pass data around, but must follow very strict rules and can not modify it in any way

thheller19:10:36

basically looking like cursors again

tony.kay19:10:24

well, the pure functional aspect is right on. passing immutable data = getting the same render result. The app state changing causes reconciler to re-trigger render.

thheller19:10:25

learned that the hard way yesterday ... https://github.com/omcljs/om/issues/425

tony.kay19:10:08

Yeah I saw you post that issue

thheller19:10:44

I'm still trying to figure out if I really don't ever need props

tony.kay19:10:56

I think it was because of the shape change, right…not the fact that you wanted to patch in data

thheller19:10:04

but that none of my data can have a :ref property already bothers me

thheller19:10:04

but I'm not used to om.next enough yet so you might not really ever need it

thheller19:10:24

still evaluating ... trust in David right now to know what is "best"

tony.kay19:10:28

Hm. right. The indexer is supposed to play the role of finding things

thheller19:10:30

the issue is that I don't want DOM-related stuff in my data

thheller20:10:09

but I really need to learn more first before making hasty judgements

thheller20:10:57

I get what David is trying to do ... hope it all works out ... bit skeptical though.

tony.kay20:10:09

I’m in the same basic spot…but as I’m working through it I’m mostly just seeing some of my assumptions were wrong, not that there is anything inherently bad. Something has to denormalize all of this state across the UI, and a graph is as good as anything. Especially when you mix in persistent/transient data mixing

tony.kay20:10:53

The thing that mostly surprised me is that the parser itself seems to need to be recursive on it’s own. The query runs on root, and then you’re done…so if you had subcomponents that want something, the top level parser has to work it out. I was hoping for the ability to disconnect subtrees that were self-rooted and could hit the “router/parser” themselves. Perhaps I’m missing something that is actually there.

thheller20:10:01

Well you can always write a second "root" component and manually add that somewhere down the tree

thheller20:10:32

but I think that's what subquery is about .. which I also haven't figured out yet

tony.kay20:10:44

I have not seen subquery

tony.kay20:10:12

A second root…how would you add it? Seems like add-root wants an existing DOM node, and it seems like a bad idea to tell add-root! to join onto something that is already under the control of react elsewhere

thheller20:10:26

well it is probably a terrible idea .. let me try and sketch what I mean

tony.kay20:10:50

I also remember from his most recent talk that “getting initial state from the server in a manner that is synchronized” is really only doable if the root node knows what all is needed. The special query syntax for remotes triggers the (remote) reads, then stuff gets merges in, I believe, depth first on the return. But I think this is also the reason for a single top-level query: you can make a single server request, gather up all the stuff, then send it back all at once.

tony.kay20:10:07

Oh, and since queries can be parameterized, it seems like you can put query fragments in your parameters…perhaps this is dynamic query. So some component that needs to make a decision about what to render could be told to vary it’s parameters such that the query changes and then the render method looks at the data and chooses which sub-component to render based on what is in the props. This lets you dynamically change out the subcomponent tree

tony.kay20:10:57

Params: { :subquery {:thing1 [:item/title]} } Query: [ ?subquery ] (set-params! c { :subquery {:thing2 [:other/data]} }) That sound roughly right?

thheller20:10:07

alright, David is probably gonna yell at me for how bad of an idea this is

thheller20:10:48

see OtherRoot which is nested inside RootView which has no query

thheller20:10:56

seems to work fine

tony.kay20:10:01

Ah…well, credit for making something work, but I agree it is none too pretty

thheller20:10:04

the remote part I actually hate, just like Falcor/Relay

thheller20:10:24

seems like a step in the wrong direction

thheller20:10:24

we got all this async, multiple connections, caching and force everything through ONE rpc endpoint.

thheller20:10:58

but I understand the motivations for it

tony.kay20:10:15

Seems you can also just bang on the atom and skip the remote stuff

thheller20:10:08

yeah, but om.next tries to do all this automatic normalization of data

thheller20:10:41

I guess I only have a problem with all that since all my data is already pretty normalized since it is living in a SQL db

thheller20:10:14

but ... still learning ... there are some quite some compelling arguments to do it this way

tony.kay20:10:34

Yeah, I’m kinda in the best circumstance: full stack clojure/clojurescript with datomic

tony.kay20:10:37

green field

tony.kay20:10:57

so I can totally drink the cool-aid with few side-effects

tony.kay20:10:02

@thheller: Hey, GF is wanting to go out. I guess since it is Sunday I oughta get off my rear. Thanks for chatting about this. I feel a bit clearer on a few more things

thheller20:10:27

hf ... helped me to think through some stuff too.