This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-07-07
Channels
- # admin-announcements (2)
- # boot (111)
- # capetown (5)
- # cider (15)
- # clara (15)
- # cljs-dev (8)
- # clojure (78)
- # clojure-brasil (1)
- # clojure-dev (2)
- # clojure-greece (25)
- # clojure-hk (1)
- # clojure-russia (5)
- # clojure-seattle (1)
- # clojure-spec (120)
- # clojure-sweden (3)
- # clojure-uk (63)
- # clojurescript (161)
- # data-science (1)
- # datomic (21)
- # editors (43)
- # emacs (3)
- # funcool (1)
- # hoplon (72)
- # instaparse (11)
- # jobs (1)
- # off-topic (2)
- # om (212)
- # onyx (9)
- # other-languages (129)
- # proton (5)
- # re-frame (15)
- # reagent (18)
- # slack-help (4)
- # spacemacs (18)
- # untangled (224)
- # yada (21)
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?
@cmcfarlen: I changed the local-read :related
defmethod, but I’m not seeing any errors
@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.
@denik: pretty sure there’s no plans to be able to transact!
from components without queries
you either want to separate your queries across more components
@anmonteiro: what about the option to no implicitly include rereads for the entire query?
or if that’s not an option, call transact!
deeper in the tree?
right
exactly because they do not query for data
if they don’t query for data, there’s nothing to be changed about it
in a transaction the component that transacts is implicitly queued
and you can’t queue a component that has no query simply because it doesn’t make sense
if it has no query, it’ regarded as stateless
so no need for it to be in the query path or the reconciling lifecycle
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.
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?
@denik: I’m not sure I follow
can you be more specific?
there are some different interpretations to your sentence above
@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?
@anmonteiro: navigate to an item /item/3
and then type in the text field.
@anmonteiro: or, eval (om/path (om/ref->any (compassus/get-reconciler app) [:item/by-id 3]))
If local-read
returns {:value nil}
then the above is [:item-details :item]
, but it should be [:compassus.core/route-data :item]
@denik: yeah, so you can do just that
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
)
@cmcfarlen: error shows up for me now
thanks
@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?
@anmonteiro: thanks!
@peeja: I don’t see what benefits arise from that
that is and should be just a dumb component
the actual component that’s responsible for the list is Root
, in your example
you're just abstracting the render stuff into that dumb component
It isn't weird to you that the Root
component here would know which component a ListOfItems
uses to render each Item
?
@peeja: what’s weird to me is that ListOfItems
would even have a query
as I said, Root
is the actual “List component"
its query should be {:the-list (get-query ListItem)}
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
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.
@peeja: sorry not following now, what’s MoreDetailedListItem
and where would that fit?
MoreDetailedListItem
would be another component that renders an item from :the-list
, but with a different query
I think you’d need a union query then
If ListOfItems
changes which component it uses to render each item, Root
has to change its query.
right
That is, even just at development time, if I change one component, I have to change another component
gotcha
I don’t see it like that, TBH
imagine you didn’t have this ListOfItems
component
your render logic for the list would be in Root
you’d still have to change the component to render then
@peeja: is your real problem that it’s on a separate component?
So maybe you didn’t understand what I said earlier, or I didn’t explain myself well enough
the actual “List” component here is Root
so it really needs to know which List item it is rendering
It feels to me like the issue here may be that there is no representation of a collection in the Om Next data model
if you don’t want to be changing the list item to rendering in 2 places, you can pass it to the “dumb” component
e.g. as a computed prop
“here’s a list, render that using this factory"
I think what you’re struggling with is that there’s no difference between 1-1 or 1-many joins
my answer to that would be that this is not Haskell 🙂
i.e. there are no types to annotate that
there’s no way to know, at compile-time, if a join is going to produce a singleton or a list
there’s no way to do that with Datomic Pull, AFAIK
so it would mean that we were not compliant with Pull anymore
and running queries “automatically” in the backend would not be possible
Okay, but if we weren't trying to be compatible with Datomic Pull, this would be completely reasonable, no?
@peeja: I could see something like that being useful, but I would not vote for your “Delegate” solution
that’s just working around the symptom IMHO
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
it’s also worth thinking if this is something that can be implemented in user-land
In Datomic Pull, is it even possible to get a singleton value? Don't you always get a collection for joins?
IMHO everything that we can avoid going in Om Next at this point is a good thing
I think that we can get a singleton
I’m not sure though
I'm not really advocating for slapping another feature on, IQueryDelegate
is just a proof of concept
makes sense that we can get singleton values
after all there’s cardinality one and many
exactly
there’s no schema in Om Next
so everything is a little bit more dynamic if you will
which leads me to the next point: maybe all that’s needed is some kind of schema for Om Next?
that’s probably something that can be a library too
and definitely sounds like an interesting problem to tackle
it’s at times like this that I wish I weren’t so busy all the time 🙂
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.
sounds good
let me know what you come up with in the end
@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.
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!
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.
@niamu: that’s not going to be possible with the current implementation
there needs to be an implementation of Sablono / Hiccup that targets Cellophane elements
@anmonteiro: Can you elaborate a bit more on what that might entail? Maybe I’ll attempt forking Sablono to add support.
@niamu: there’s an example thing here: https://github.com/arohner/foam/blob/master/src/foam/html.clj
cellophane.dom
is heavily influenced by Foam
so that would be a good start
maybe also worth looking at the hiccup source rather than Sablono's
@niamu: actually there’s maybe one other thing you might want to look at
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
actually that’s converting from enlive to cellophane.dom
so maybe nevermind 🙂
¯\(ツ)/¯
@cmcfarlen: I’m looking at it currently
I’ve ported your example to plain Om Next without Compassus and I don’t seem to see the error
so maybe something fully Compassus related
@anmonteiro: Cool, thanks. Let me know if you need anything from me!
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
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.
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.
@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)
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?
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.
So if your init-state had {:comment/by-id {1 {...} 2 {...}}}
then you could go that route
It seems like the usual thing to do is start with a big denormalized tree and let om do its stuff
@cmcfarlen: seems like it’s an Om Next problem after all, traced it to path-meta
too
@anmonteiro: interesting
the problem is that you’re declaring :item/name
, :item/description
, :item/id
and :item/other
in Item
’s query
but the result doesn’t have :item/other
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
the problem is not even that
it’s the absence of :related
that we set to nil
which makes sense since that’s how we reproduced the issue
those are the props that are up in the union, :item
and :related
at the moment it’s kind of a weak requirement, but I don’t know how to work around that
or rather a strong requirement, and a weak assumption
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
I’ll investigate it further, but no promises
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
I worked around my other issue by writing a function that combines those separated queries, but that is a strange one as well
Ultimately though, these problems are stemming from depending on a remote read to produce other remote reads to get all the data I need
@cmcfarlen: I didn’t read the other issue yet, I’ll let you know what I think later
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.
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}))
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"}]
.
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.
this should be the relevant bits: https://gist.github.com/curtosis/974b5e9da984a2ed9eb53c81d19adbea
ClojureScript should probably warn/error like Clojure does if people try to use private stuff
I think it's also not (at least not casually) intuitive why it's get-state
, get-computed
, but props
.
but some of these design decisions revolved around protocols fns clashing with user fns