Fork me on GitHub
#om
<
2016-07-07
>
tmulvaney12:07:07

Hi everyone, I've been having fun learning om.next but am currently stumped. I have a list of posts and a single post can be selected at any given time. The app-state is essentially {:post/selected {:post/id 42} :posts [...]}. Each of my PostItems in the list have a query like '[:post/name :post/date [:post/selected _]]. I use the link to :post/selected to change the class of the PostItem node if its id matches. This works, but is really slow to update the UI when a post is selected. Any thoughts on how best to do this?

anmonteiro12:07:02

@cmcfarlen: I changed the local-read :related defmethod, but I’m not seeing any errors

denik12:07:46

@ethangracer: I’d like to re-query for only the state that changed. First of all it would be nice if the system just knew what state changed. That would eliminate my problem. But instead it unintelligently reruns the entire query.

anmonteiro12:07:08

@denik: pretty sure there’s no plans to be able to transact! from components without queries

denik12:07:35

😢 🙂

denik12:07:51

feeling bipolar about that

anmonteiro12:07:34

you either want to separate your queries across more components

denik12:07:52

@anmonteiro: what about the option to no implicitly include rereads for the entire query?

anmonteiro12:07:06

or if that’s not an option, call transact! deeper in the tree?

denik12:07:29

I can’t call it deeper in the tree if the component has no query

denik12:07:34

that’s the issue I’m running into

denik12:07:53

why should query-less components not be able to transact?

anmonteiro12:07:20

exactly because they do not query for data

denik12:07:28

I’d say they are quite rare, and usually to be found at the end of a path

anmonteiro12:07:43

if they don’t query for data, there’s nothing to be changed about it

denik12:07:57

the transaction can still have re-read keys

anmonteiro12:07:05

in a transaction the component that transacts is implicitly queued

anmonteiro12:07:17

and you can’t queue a component that has no query simply because it doesn’t make sense

anmonteiro12:07:24

if it has no query, it’ regarded as stateless

anmonteiro12:07:52

so no need for it to be in the query path or the reconciling lifecycle

denik12:07:15

Good. I see your point. I think the real problem is not that stateless components have no queries, but that it’s hard to know what changed in a transaction.

denik12:07:35

e.g. I could have a bunch of computed props passed down from some other component higher up and run a transaction based on that data. If I don’t include the components query in the transact, that new data (post-tx) will not be reconciled and the affected component will not update, correct?

anmonteiro13:07:52

@denik: I’m not sure I follow

anmonteiro13:07:55

can you be more specific?

anmonteiro13:07:19

there are some different interpretations to your sentence above

denik13:07:52

@anmonteiro: Om.next makes the assumption that the relevant data to re-query after a given transaction can be derived from the transacting component's query + optional reads. My question is: doesn’t it seem like there should be a way for Om to know precisely which data changed and re-render components based on that?

cmcfarlen14:07:20

@anmonteiro: navigate to an item /item/3 and then type in the text field.

cmcfarlen14:07:36

@anmonteiro: or, eval (om/path (om/ref->any (compassus/get-reconciler app) [:item/by-id 3]))

cmcfarlen14:07:27

If local-read returns {:value nil} then the above is [:item-details :item], but it should be [:compassus.core/route-data :item]

anmonteiro14:07:58

@denik: yeah, so you can do just that

anmonteiro14:07:10

alternatives: 1) transact! and use idents as follow-on reads 2) transact! on the reconciler and use follow-on reads there (which are not transformed with transform-reads)

anmonteiro14:07:33

@cmcfarlen: error shows up for me now

peeja14:07:06

@anmonteiro: You were explaining to me a while back why a component isn't allowed to reuse another component's query. I agree that it makes sense from a data/UI mapping/modeling perspective, but I'm still concerned by the fact that a component which renders a list-like component has to know about the list component in its render, but know about the list item component in its query. It feels to me like it shouldn't know about the list item; the list should know that. I've got a simple, if naive, solution: rather than implement IQuery, the list can implement an IQueryDelegate; if a component implements IQueryDelegate, then get-query returns the delegate's query. The query's metadata points to the delegate (the list item), not the list component. The root query ends up the same, it's just derived with an extra layer of indirection. Demo implementation here: https://github.com/Peeja/om-next-starter/blob/list-noodling-2/src/om_starter/core.cljs (Assume IQueryDelegate would be in om.next, and that the get-query here would become the new om/get-query.) I'm sure there are issues with this approach, but it feels like it points in a good direction to me. Any thoughts come to mind?

anmonteiro15:07:29

@peeja: I don’t see what benefits arise from that

anmonteiro15:07:13

that is and should be just a dumb component

anmonteiro15:07:27

the actual component that’s responsible for the list is Root, in your example

anmonteiro15:07:45

you're just abstracting the render stuff into that dumb component

peeja15:07:47

It isn't weird to you that the Root component here would know which component a ListOfItems uses to render each Item?

peeja15:07:02

The feels like a violation of abstraction to me

peeja15:07:43

If I change which component ListOfItems uses, I have to change Root's query to match.

peeja15:07:08

That feels like unnecessary connaissance to me.

anmonteiro15:07:18

@peeja: what’s weird to me is that ListOfItems would even have a query

anmonteiro15:07:36

as I said, Root is the actual “List component"

anmonteiro15:07:51

its query should be {:the-list (get-query ListItem)}

peeja15:07:41

It's responsible for which list, but it's not responsible for knowing that it's ListItem's query that it needs to get for each item of that list

peeja15:07:54

How would it know to use ListItem and not MoreDetailedListItem?

peeja15:07:05

We could have a very clear rule that if you render a component in your render, you use its query in your query. Instead we've got this weird abstraction violation.

anmonteiro15:07:07

@peeja: sorry not following now, what’s MoreDetailedListItem and where would that fit?

peeja15:07:40

MoreDetailedListItem would be another component that renders an item from :the-list, but with a different query

peeja15:07:45

(presumably more details)

anmonteiro15:07:07

I think you’d need a union query then

peeja15:07:33

No, I want to render every item with MoreDetailedListItem now

peeja15:07:45

If ListOfItems changes which component it uses to render each item, Root has to change its query.

peeja15:07:06

That is, even just at development time, if I change one component, I have to change another component

peeja15:07:22

That violates what could be a nice clean encapsulation

anmonteiro15:07:44

I don’t see it like that, TBH

anmonteiro15:07:08

imagine you didn’t have this ListOfItems component

anmonteiro15:07:33

your render logic for the list would be in Root

anmonteiro15:07:43

you’d still have to change the component to render then

anmonteiro15:07:01

@peeja: is your real problem that it’s on a separate component?

peeja15:07:33

My problem is that Root has to know which component ListOfItems renders.

peeja15:07:47

In my mind, that shouldn't be any of Root's business.

peeja15:07:17

If I call a function, I don't expect to have to know what functions it calls.

anmonteiro15:07:41

So maybe you didn’t understand what I said earlier, or I didn’t explain myself well enough

anmonteiro15:07:54

the actual “List” component here is Root

anmonteiro15:07:11

so it really needs to know which List item it is rendering

peeja15:07:49

It feels to me like the issue here may be that there is no representation of a collection in the Om Next data model

anmonteiro15:07:52

if you don’t want to be changing the list item to rendering in 2 places, you can pass it to the “dumb” component

anmonteiro15:07:11

e.g. as a computed prop

anmonteiro15:07:23

“here’s a list, render that using this factory"

peeja15:07:39

I want Root to be responsible for picking a list, but not for the list itself

anmonteiro15:07:36

I think what you’re struggling with is that there’s no difference between 1-1 or 1-many joins

peeja15:07:58

That's it exactly

peeja15:07:06

I don't understand why that is

anmonteiro15:07:48

my answer to that would be that this is not Haskell 🙂

anmonteiro15:07:19

i.e. there are no types to annotate that

peeja15:07:36

I'm not sure why you need types…

anmonteiro15:07:06

there’s no way to know, at compile-time, if a join is going to produce a singleton or a list

peeja15:07:22

But that's just a failing of the syntax.

peeja15:07:29

There could be a way

peeja15:07:42

You couldn't prove it's correct statically, but that's fine

anmonteiro15:07:51

there’s no way to do that with Datomic Pull, AFAIK

peeja15:07:12

Ah, so…it's an inherited flaw? 🙂

anmonteiro15:07:22

so it would mean that we were not compliant with Pull anymore

peeja15:07:25

I mean, it's not inherent to the approach

anmonteiro15:07:42

and running queries “automatically” in the backend would not be possible

peeja15:07:06

Okay, but if we weren't trying to be compatible with Datomic Pull, this would be completely reasonable, no?

anmonteiro15:07:03

@peeja: I could see something like that being useful, but I would not vote for your “Delegate” solution

anmonteiro15:07:17

that’s just working around the symptom IMHO

peeja15:07:24

Yeah, I agree.

peeja15:07:47

I really want the ListOfItems to actually be responsible for a real query, because I want there to be a collection node in the graph that it's associated with

anmonteiro15:07:29

it’s also worth thinking if this is something that can be implemented in user-land

peeja15:07:49

In Datomic Pull, is it even possible to get a singleton value? Don't you always get a collection for joins?

anmonteiro15:07:36

IMHO everything that we can avoid going in Om Next at this point is a good thing

peeja15:07:55

Yeah, fair enough 🙂

anmonteiro15:07:08

I think that we can get a singleton

anmonteiro15:07:22

I’m not sure though

peeja15:07:39

I'm not really advocating for slapping another feature on, IQueryDelegate is just a proof of concept

anmonteiro15:07:43

makes sense that we can get singleton values

anmonteiro15:07:55

after all there’s cardinality one and many

peeja15:07:57

I'm really wondering if it points to a deeper problem with the model

peeja15:07:16

Oh, yeah, the existence of a schema makes that possible

anmonteiro15:07:29

there’s no schema in Om Next

anmonteiro15:07:46

so everything is a little bit more dynamic if you will

anmonteiro15:07:26

which leads me to the next point: maybe all that’s needed is some kind of schema for Om Next?

peeja15:07:46

That sounds pretty awesome on the face of it

anmonteiro15:07:08

that’s probably something that can be a library too

peeja15:07:15

Yeah, that makes sense

anmonteiro15:07:22

and definitely sounds like an interesting problem to tackle

anmonteiro15:07:33

it’s at times like this that I wish I weren’t so busy all the time 🙂

peeja15:07:01

In my case, maybe it's reasonable for Root just to know that ListOfItems is a list, and we need to use its list item's query instead, and therefore have it ask ListOfItems for the query for its list item.

peeja15:07:17

Basically IQueryDelegate, but without magically switching inside get-query

peeja15:07:33

Root can know enough to ask a more specific question

peeja15:07:07

which would be entirely reasonable to implement on an app-by-app basis

anmonteiro15:07:55

let me know what you come up with in the end

peeja15:07:03

Will do :thumbsup:

cmcfarlen16:07:48

@anmonteiro: I think I have narrowed this path problem down a bit. I noticed that pushy's dispatch fn gets called twice (for some reason) and that its only after the second that the om-path is set correctly. I made a change in compassus to make set-route! only call transact! if the given route is different than the current one. Now just the presence of the :related join is enough to trigger the issue. And, you can now see that it fails when loading the item details directly, but works if you link from the list page.

cmcfarlen16:07:18

I feel like this might be caused by some interaction with om/transform-reads and om.next.impl.parser/path-meta, but I'm not sure how to debug path-meta, its pretty hairy!

niamu16:07:52

Does anyone have any experience with using Om Next and doing server-side rendering in hiccup/sablono markup? I’m currently trying out cellophane (https://github.com/ladderlife/cellophane) and it works very well up until I’ve attempted to use sablono/hiccup markup for rendering.

anmonteiro17:07:52

@niamu: that’s not going to be possible with the current implementation

anmonteiro17:07:39

there needs to be an implementation of Sablono / Hiccup that targets Cellophane elements

niamu17:07:53

@anmonteiro: Can you elaborate a bit more on what that might entail? Maybe I’ll attempt forking Sablono to add support.

anmonteiro17:07:37

cellophane.dom is heavily influenced by Foam

anmonteiro17:07:46

so that would be a good start

anmonteiro17:07:00

maybe also worth looking at the hiccup source rather than Sablono's

niamu17:07:36

Thanks a lot. I’ll start digesting this. 🙂

anmonteiro17:07:28

@niamu: actually there’s maybe one other thing you might want to look at

anmonteiro17:07:04

I’ve possibly done all the heavy lifting of translating hiccup-like stuff to cellophane.dom here: https://github.com/ladderlife/cellophane/blob/master/perf/cellophane/perf.clj

anmonteiro17:07:04

actually that’s converting from enlive to cellophane.dom

anmonteiro17:07:20

so maybe nevermind 🙂

anmonteiro17:07:54

@cmcfarlen: I’m looking at it currently

anmonteiro17:07:17

I’ve ported your example to plain Om Next without Compassus and I don’t seem to see the error

anmonteiro17:07:28

so maybe something fully Compassus related

cmcfarlen17:07:56

@anmonteiro: Cool, thanks. Let me know if you need anything from me!

cmcfarlen17:07:41

I found another issue, but I'm not yet sure if its compassus related. I'm going to try to reproduce it in a smaller example as well

cmcfarlen17:07:27

The initial remote query is something like [{:item-details [{:item [...]} {:related-a [...]} {:related-b [...]}}] which works fine and everything is normalized correctly. When I do something like (om/transact! this '[(update/item {}) :related-a :related-b]), the remote query comes out [{:item-details [{:related-a [...]}]} {:item-details [{:related-b [...]}]}], which causes a problem with normalizion. Only the first :related-a is normalized, the other is stored denormalized. Switching the order in the re-read list switches which gets properly normalized.

tmulvaney17:07:05

I've run into something which might be an issue or I may just have missed something. I've made a gist here to explain it: https://gist.github.com/thomasmulvaney/01f7d5ea50e4f6de9bcd22d31291cd22 I've got two roots: BadRoot which normalises the db incorrectly (although i think it should?) and GoodRoot which does but I don't like it as it contains a query which is not going to be used. Any help would be appreciated.

cmcfarlen17:07:05

@tmulvaney: This depends on how your reconciler is initialized, but you might try putting all of the comment info in the tree of posts (meaning, make init-state totally denormalized)

tmulvaney17:07:13

ah thanks @cmcfarlen. That makes sense. I was just trying to see how om.next handled normalisation. You mentioned that maybe it has something to do with how the reconciler initialised? Are you suggesting that I could keep it normalised but I would need to initialise it differently?

cmcfarlen18:07:36

Well, your root query would either have to reflect that the :comments key contains the comment info (as in GoodQuery), or you could give om your state denormalized. I think if you give the reconciler a plain map it will just use that as the state, but if you give it an atom it will try to normalize it with the root query.

cmcfarlen18:07:32

So if your init-state had {:comment/by-id {1 {...} 2 {...}}} then you could go that route

cmcfarlen18:07:03

It seems like the usual thing to do is start with a big denormalized tree and let om do its stuff

anmonteiro18:07:03

@cmcfarlen: seems like it’s an Om Next problem after all, traced it to path-meta too

cmcfarlen18:07:25

path-meta is a beast, haha

anmonteiro18:07:54

the problem is that you’re declaring :item/name, :item/description, :item/id and :item/other in Item’s query

anmonteiro18:07:12

but the result doesn’t have :item/other

cmcfarlen18:07:46

I wouldn't expect that to be an issue.

cmcfarlen18:07:24

Does that cause path-meta to stop too soon or something?

anmonteiro18:07:58

I’ll look into it a bit further, however it’s the only way we can select a union query’s branch at the moment

anmonteiro18:07:08

the problem is not even that

anmonteiro18:07:15

it’s the absence of :related

anmonteiro18:07:19

that we set to nil

anmonteiro18:07:33

which makes sense since that’s how we reproduced the issue

anmonteiro18:07:04

those are the props that are up in the union, :item and :related

cmcfarlen18:07:32

Oh, so we need to ensure that queries in the union are fully fulfilled?

anmonteiro18:07:36

at the moment it’s kind of a weak requirement, but I don’t know how to work around that

anmonteiro18:07:54

or rather a strong requirement, and a weak assumption

cmcfarlen18:07:55

That's subtle, but makes sense. Is there an improvement we can make to om to relax that one? I see it biting people pretty often as more unions are used

anmonteiro18:07:22

I’ll investigate it further, but no promises

cmcfarlen18:07:34

OK, sounds good. I feel better understanding it. It seems fine to just return an empty map for :related which is what I ended up doing in my real app

cmcfarlen18:07:51

Thanks for taking time to look at it!

cmcfarlen18:07:15

I worked around my other issue by writing a function that combines those separated queries, but that is a strange one as well

cmcfarlen18:07:56

Ultimately though, these problems are stemming from depending on a remote read to produce other remote reads to get all the data I need

anmonteiro19:07:48

@cmcfarlen: I didn’t read the other issue yet, I’ll let you know what I think later

curtosis21:07:14

I'm probably missing something incredibly simple, but what would be a reason props getting passed to a component show up as just [object Object]? It's super basic: I'm calling the factory for the component with a map of the props it should get.

dnolen21:07:25

@curtosis: going to need more information

dnolen21:07:32

we cannot use real React props

dnolen21:07:43

so we make a JS object with a bunch of stuff in it

curtosis21:07:00

Looks like this:

(defui ^:once MenuItem
  Object
  (render [this]
    (let [{:keys [id label icon tabkey] :as spec} (om/get-props this)]
      (menu-item-dom-code this spec))))

(def menu-item-factory (om/factory MenuItem {:keyfn :id}))

dnolen21:07:40

that snippet doesn’t tell me anything

dnolen21:07:50

[object Object] is JS printing stuff

curtosis21:07:37

and then in the parent component I'm doing (map menu-item-factory menu-items), where menu-items is along the lines of [{:id 1 :label "One"}{:id 2 :label "Two"}].

curtosis21:07:22

the menu-items aren't managed by om; it's just a static vector of maps defining what the menu should look like. But it seems like elsewhere I've been able to just pass in a map to the factory and have it work.... but I could be misremembering completely.

dnolen21:07:51

@curtosis: you should probably gist a a complete small example

curtosis21:07:45

it's om/props, not om/get-props.

curtosis21:07:38

a full half-hour I've been staring at that.

curtosis21:07:40

sorry for the trouble! probably time to walk away from the screen for a bit.

curtosis21:07:46

didn't catch it, because get-props also exists.

dnolen21:07:13

ClojureScript should probably warn/error like Clojure does if people try to use private stuff

curtosis21:07:29

would be useful at times, yeah.

curtosis21:07:14

I think it's also not (at least not casually) intuitive why it's get-state, get-computed, but props.

dnolen21:07:52

will take a look at that Friday, probably trivial to make that warning

curtosis21:07:42

not super critical, of course, but cool. 🙂

dnolen21:07:06

naming is hard

dnolen21:07:25

but some of these design decisions revolved around protocols fns clashing with user fns

dnolen21:07:06

the ClojureScript convention of -first is OK for stuff people are rarely going to implement

dnolen21:07:11

not true for defui

curtosis21:07:17

naming is the one hard problem in CS, after all. 😉

jasonjckn22:07:49

any suggestions for getting back button to work with om.next?

jasonjckn22:07:06

I was thinking just using om/from-history -1 , and reset! on app-state

jasonjckn22:07:12

with HTML5History pushState

jasonjckn23:07:56

is there a hook for post transact!

jasonjckn23:07:09

i want to set URL everytime a transaction occurs