Fork me on GitHub
#om
<
2015-12-28
>
jimmy05:12:33

@robert-stuttaford: I would encourage you to look at https://github.com/swannodette/om-next-demo . It might be outdated but when you work around it, you will find some useful functions to test and work along the way.

maackle11:12:57

I'm having trouble understanding the reconciler's :send callback. I've got a parser with remote reads that asynchronously merges in novelty with the send callback. It works great when the page loads: when the XHR finishes the send callback is called and everything updates. However I also have a mutation that updates state. When I query that mutation along with a bunch of other keys I want to read (which trigger the same XHR), everything ALMOST works. The state gets updated, the novelty merged in (asynchronously, after the XHR returns), but the read queries don't seem to fire after the state gets the novelty from the send callback, so nothing gets re-rendered. My workaround for now is to swap a dummy value into the app atom every so often to force a re-render, which is obviously not ideal. Re-rendering also happens if I update some component local state. I know this is a vague description, but I'm hoping somebody will have had experience and can give a pointer or two. If not I'll boil it down to a simpler case to share.

robert-stuttaford13:12:05

so, i’m probably tackling too many things at once, here, but here goes. i’ve got a homogenous, nested record structure (name, id, children->[(name, id, children),…]). i have it rendering this tree in a recursive manner just fine, but i’m struggling with mutating it. the tree structure is loaded via XHR, and, as best as i can tell, the data is not normalised by om.next when the remote merges it in. is there a way to get om.next to normalise the merged-in data as well, and is there a way to see whether it’s doing this or not?

jannis13:12:57

@robert-stuttaford If you use merge!, the incoming data will be normalized. You may have to pass your own :merge-tree to the reconciler to make merging novelty work (to avoid overwriting properties set only in the client for instance). If you print @reconciler in a browser REPL, that'll show you what the data looks like after normalization.

robert-stuttaford13:12:59

ok. the data is currently coming in via the automatic merging that om does with :remote true

robert-stuttaford13:12:52

i’ll do the @reconciler thing. i was printing state from inside a mutate impl but that showed me the nested maps

jannis14:12:08

Are your records lists, like your post suggests or are they nested maps? If they are, it might be a good idea to transform them to maps (e.g. {:name ..., :id ..., ...}. One way to look at Om queries is as statements that (recursively) select keys (properties) from a nested map (your app state). Om is actually more flexible than that but your read implementation will be simpler the closer your apo state resembles a nested map.

robert-stuttaford14:12:03

they’re nested maps

jannis14:12:52

@robert-stuttaford Look for default-merge-tree in next.cljs. That's the default merge algorithm. It's expected that most people will have to overload it when constructing the reconciler because it's very, very simple.

robert-stuttaford14:12:54

i note that normalised data uses idents. how do idents get their names? :person/by-name

jannis14:12:52

Idents are just two-element vectors. I'm not even sure the first element needs to be a keyword. The naming is up to you.

jannis14:12:02

It could be [:person 19] or [:db/id 18] or whatever.

robert-stuttaford14:12:44

gotcha. ok. data isn’t denormalised. going to look into :merge-tree now

robert-stuttaford14:12:53

so, reading https://github.com/omcljs/om/wiki/Components%2C-Identity-%26-Normalization, it seems to suggest that om came up with the :person/by-name key all by itself

jannis14:12:01

In the normalized data, this will lead to two map entries: :person {19 {:id 19, :name John ...}} (a lookup table essentially) and :people [[:person 19]] (a vector of links, where :people is an entry in your "unnormalized" data that you query for and that originally contained a vector of maps - the original persons records).

robert-stuttaford14:12:25

ohhhh. it’s in the om/Ident declarations

jannis14:12:33

No, you specify the idents yourself, in the components.

robert-stuttaford14:12:15

things are snapping into focus a little simple_smile

robert-stuttaford14:12:06

looks like i shouldn’t give om a tree structure and rather just give it pre-denormalised data with idents in place

jannis14:12:36

Om essentially looks at the root query and the denormalised state data side by side. The query is annotated with the origin components in its meta data. So when normalization looks at a subquery that corresponds to a component and a record in the data, it passes the record to the component's om/Ident, replaces the record with the resulting link and puts the actual record data into the lookup table using update-in @state link merge data (well, simplified but roughly like that). That's how the normalized data is constructed.

robert-stuttaford14:12:22

so, how do i model recursion in om/IQuery decls?

robert-stuttaford14:12:38

i totally get doing this in the non-recursive case, now. i’m trying to reach a bit further simple_smile

jannis14:12:55

No, you don't have to put idents in place. But your records need to have a structure that your om/Ident implementations can turn into idents. If the ident is computed from an :id property, your records will have to have the structure {:id ...}.

robert-stuttaford14:12:04

yes, i have this. i’m now working on how to relate the recursive component’s query to the root component’s query

jannis14:12:48

Recursion is done via {:somekey (om/get-query SomeComponent)} or {:children '...} if the children will be rendered using the same component.

robert-stuttaford14:12:47

ok. then i’m on the right path. i have the first case. but when i pr-str the om/get-props value in the recursive component, i see the idents instead of the actual maps

robert-stuttaford14:12:51

it feels like i’m really close

jannis14:12:59

Ah. In your read you'll have to translate back from normalized data to the real data (to expand the ident links again for instance).

jannis14:12:15

That's what om/db->tree is for.

jannis14:12:00

(let [st @state] {:value (om/db->tree query (get st :somekey) st)}) for instance.

robert-stuttaford14:12:02

@jannis, can i put a gist together for you to review?

wilkerlucio14:12:35

what method can I use to set the component initial state?

wilkerlucio14:12:28

found the answer, use the initLocalState lifecycle method

robert-stuttaford15:12:32

curious: what are you doing with om.next, @jannis?

jannis15:12:39

@robert-stuttaford: I'm just playing with it, toying with different ideas involving Git, Clojure and Om web apps to eventually be comfortable looking for related work. My main pet projects at the moment are Gitalin (a Git-based data store with a Datalog-like query interface), Custard (a requirements/architectore/work planning tool for hacker-friendly, no-nonsense software architecture) and a somewhat secret project with @thosmos simple_smile

jplaza15:12:26

@jannis would you recommend using it now to build a production app? I have to rewrite an web admin dashboard and turn it into a SPA. We use Clojure for our backend and I would like to use it for our frontend to. I would hate to have to go back to JS

jannis15:12:41

@jplaza: Sure, technically I don't see a reason not to use it. Personally, I wouldn't want to go back to anything else at this point, having worked with Om Next for a few months. There may be other factors in the decision for your company, like how much training your team will need to adopt it, how much time pressure there is etc.

jannis15:12:32

@jplaza: The two main areas where the story is still a little rough perhaps and where more experiments will be needed to come up with elegant solutions are routing and animations.

jplaza16:12:40

I can deal with the experimentation simple_smile .. we are a Startup so that’s our “daily bread”. But something like secretary would help with routing, right? At least until a more suitable solution comes up

dnolen16:12:44

fwiw I just don’t see routing as a real problem

dnolen16:12:53

this is just a thing that web developers like

dnolen16:12:02

no serious UI thing has ever needed this ever

dnolen16:12:31

the only reason I’m spending any time on it is because people keep asking the same question over and over and over again

jplaza16:12:09

@dnolen: so how do you think SPAs should approach this?

dnolen16:12:18

if all you need a custom routing thing - there’s enough there to sort this out yourself

dnolen16:12:29

people already have a bunch of various solutions

dnolen16:12:04

there will never be a standard thing anyway because I simply do not care about it

dnolen16:12:13

nor do I think it makes any sense at all in the general case

dnolen16:12:20

React Native where you’re not even going to have links!

dnolen16:12:42

however due to popular demand - I am interested in a standard places to hook in whatever routing thing you like

dnolen16:12:04

my point being, Om isn’t tied to the web

dnolen16:12:12

and if that’s a deal breaker for you - you will have to go elsewhere

jplaza16:12:21

That’s a good point, but why do people keep asking about routing? Seems to me that using something like secretary would do the job

dnolen16:12:41

because A) people don’t understand how Om works yet - it’s just too soon

dnolen16:12:55

B) the obvious things aren’t completely suited for it yet

dnolen16:12:12

C) I haven’t pointed out less obvious things that are probably better for it

jplaza16:12:38

Ok fair enough… I’ll continue working in my new project and will let you guys know how things went.

jannis16:12:36

The only reason I care about routing is to allowing visitors/users to re-enter a particular UI state from a URL.

anmonteiro16:12:15

@jplaza: FWIW, check out aemette, a lein template I put together which comes with client side routing (https://github.com/anmonteiro/aemette)

jannis16:12:22

This implies a two-way translation between URLs and state. I'm not even sure I'd call that routing. 😉

jannis16:12:13

But I agree with @dnolen, the pieces are already there so you can achieve routing already. Except it may not be the most convenient way, hence the thinking about standard hooks.

jplaza16:12:41

Thanks @anmonteiro will check that out

robert-stuttaford16:12:24

@dnolen: on routing, all fair points. if you’re interested, i can definitely share a couple strong user-centric cases for routing simple_smile

robert-stuttaford16:12:44

i’m super glad that you’ve pushed it to the edges; that’s where it belongs!

dnolen16:12:57

@robert-stuttaford: I remain completely unconvinced in the value of routing

dnolen16:12:05

it’s just a web thing

dnolen16:12:26

you will never see people doing generic UI work ever bother with this

robert-stuttaford16:12:47

yeah. it’s a web thing. it allows us to provide users with good continuity - leave SPA to go to some other page - hit back, SPA renders the way it was when you left it

dnolen16:12:56

right but I don’t care about web stuff

dnolen16:12:59

I care about UI programming

dnolen16:12:19

no decisions in Om have ever been about the web ever

robert-stuttaford16:12:20

that’s one. the other is sharing state with others. e.g., providing a deep link to some record’s view

dnolen16:12:37

right but web style routing is a solution

dnolen16:12:50

one of many possible solutions and not even a particular compelling one

robert-stuttaford16:12:51

i hear you simple_smile Om is generic to the factor in which it runs - native, appletv, web, etc

robert-stuttaford16:12:33

it’s the one we fall back on mostly because it’s the one we’ve always used, i guess. and browsers know how to do it. just like http caching

dnolen16:12:55

http caching has nothing to do with browsers

dnolen16:12:02

http clients implement this more generally

robert-stuttaford16:12:31

i’m curious; what other approaches do you see to solving this problem that don’t involve browser history?

dnolen16:12:40

http clients of any kind

robert-stuttaford16:12:52

(assuming a good case can be made for needing it)

dnolen16:12:06

if your data end point is HTTP based

dnolen16:12:13

you will want http client caching

dnolen16:12:31

I have never seen any serious http client lib that did not support this

robert-stuttaford16:12:54

@dnolen: i’m finally taking a stab at om.next. busy doing a recursive tree (since doing that with the previous om proved to be so useful for learning). i’ve run into a snag. i’ve pm’d you gist url. i’d greatly appreciate it if you can give me some idea of why i’m getting the included stacktrace at the linked line, please, when you have a moment?

dnolen16:12:23

@robert-stuttaford: I don’t take PMs about Om questions

dnolen16:12:26

this channel exists for a reason

robert-stuttaford16:12:41

ok. i’ll sanitise the data and link it in here

dnolen16:12:47

people have to be realistic about my time these days

dnolen16:12:52

or I will stop answering questions

robert-stuttaford16:12:57

you’re right. my bad

robert-stuttaford16:12:26

using [org.omcljs/om "1.0.0-alpha28”]. i’m going to keep digging, of course. just sharing as i might be experiencing a bug or a common but undocumented gotcha

anmonteiro16:12:13

@robert-stuttaford: you are loop-sending to the remote

anmonteiro16:12:27

at some point you're gonna have to return local data

anmonteiro16:12:37

when it gets merged to your state

robert-stuttaford16:12:51

i only see one XHR call, though

robert-stuttaford16:12:11

the error happens when it’s attempting to do that merge

anmonteiro16:12:31

what if you replace {:value (om/db->tree query v st) :remote true} with {:value (om/db->tree query v st)} in the line you highlighted?

robert-stuttaford16:12:32

or do i misunderstand?

robert-stuttaford16:12:59

same thing happens

anmonteiro16:12:28

let me take a closer look

robert-stuttaford16:12:36

i’m pretty sure it’s not looping the remote anyway, because before i got to doing db->tree, it was also only doing it once, but attempting to render the ident values directly

robert-stuttaford16:12:52

that it’s blowing the stack tells me that it’s looping infinitely somehow. i’ve checked and the data doesn’t have any …. humm. i might have an infinite loop in my data 😞

robert-stuttaford16:12:19

@anmonteiro: thanks for rubber ducking. i have an attack vector!

robert-stuttaford16:12:40

we could well have two classes of entity with identical slug values

anmonteiro16:12:18

@robert-stuttaford: am I wrong or is there something wrong with your normalized data anyway?

anmonteiro16:12:10

children is not correct in all cases

anmonteiro16:12:26

e.g. "loose-suites"

robert-stuttaford17:12:26

thank you, i’ll double check that it’s consistent

anmonteiro17:12:54

@robert-stuttaford: it also seems that there is a list in the app state while Om only supports maps & vectors

anmonteiro17:12:07

In the place I pointed out before

anmonteiro17:12:14

Not sure if this could be it

anmonteiro17:12:36

It's just what caught my eye

robert-stuttaford17:12:15

@anmonteiro: the lists not being vectors thing was the issue. turns out i had no duplicate slugs. sharp eye, thank you!

anmonteiro17:12:06

happy to help simple_smile

domkm18:12:33

Looking for advice: I'm getting started with Om Next and finding it a little confusing (not unexpected since it's still alpha). My situation is that I have Datomic->DataScript streaming sync working (sending the entire DB and then every transaction because I have a very small dataset) so all reads are local and I don't think any of Om Next's normalization stuff is relevant. All (or virtually all) mutations are remote; I don't care about optimistic updates for this project. The changes caused by mutations are transacted into DataScript via the same streaming mechanism as all other changes, so my remote mutations don't need any special handling for merging. Is Om Next worth it for this project given the above conditions and a tight deadline? Would I be better off using something like Rum (with a small custom mixin for DataScript) or will Om Next be faster to develop with after I get up-to-speed?

jannis18:12:54

@robert-stuttaford: Ah, lists vs vectors. I've hit that before.

peeja18:12:27

It feels odd to me that each read invocation is responsible for deref'ing the app state. Doesn't that mean that a single query could read from multiple versions of the app state, one for each key?

peeja18:12:45

I feel like I'm missing something philosophically.

dnolen18:12:12

@peeja: the transaction vector is ordered

dnolen18:12:25

it’s quite useful to be able to read after a mutation

peeja18:12:11

Oh, interesting

dnolen18:12:13

@domkm if you’re on a tight timeline don’t use Om Next, it’s really that simple

dnolen18:12:56

there are still a few remaining significant gaps that would keep me from using it on any production project with a timeline in the very near future

dnolen18:12:09

for longer term things you’re likely fine

domkm18:12:22

@dnolen: Oh. Thanks for the advice. What gaps are those?

dnolen18:12:27

shooting to wrap up the big gaps in January

dnolen18:12:48

some form of standard error handling / propagation. routing hooks. bug fixes.

dnolen18:12:59

@peeja: that said there isn’t nothing fundamental to :read and :mutate

dnolen18:12:07

only parse is fundamental

dnolen18:12:17

if you don’t like how the standard parse works - come up with your own thing

peeja18:12:59

Oh, I see, you could decide to give parse a map instead of an atom as the :state, then?

dnolen18:12:10

you can do whatever you want

dnolen18:12:17

it really doesn’t matter at all

dnolen18:12:29

the only contract is [env key params] for the signature

peeja18:12:47

Well, okay, you say that, but as someone who's still learning, there are definitely somethings in Om where it matters what you do. simple_smile

dnolen18:12:49

in local mode you must return a ui data tree - in remote mode a query expression

dnolen18:12:31

@peeja: sure but just pointing out that you are just dealing with whatever defaults I could come up with

dnolen18:12:44

but supplied a way to change my mind at anytime

dnolen18:12:53

or any user can change their mind for that matter

peeja18:12:17

Yeah, that makes sense. Thanks!

peeja18:12:46

Really what I'm trying to understand is: where is it sensible to "do whatever I want", and where would I be abusing something that happens to work at the moment

peeja18:12:03

and that helps me understand that

dnolen18:12:13

@peeja: you will know you can’t do whatever you want, it just cannot be made to work

dnolen18:12:23

the relationships between functions is more or less set in stone

dnolen18:12:26

their implementations are not

peeja18:12:43

Ah, that's good to know.

dnolen18:12:52

so how parse works is up to you - but I supply a productive default

dnolen18:12:10

whatever you provide must satisfy the parse contract which I described above

peeja18:12:20

Oh, wait. Does parser not even know what :state means?

dnolen18:12:32

@peeja: re-read what I said simple_smile

peeja18:12:46

Haha! That's brilliant! I get it now simple_smile

domkm18:12:54

@dnolen: By tight timeline I mean several months and January isn't far off. Assuming OmNext is complete, what does it provide if you don't need normalization, remote reads, or optimistic updates? Sorry if this is a foolish question. I've watched all the talks and read all the docs and I'm still not sure.

dnolen18:12:13

@peeja: if you think about it it has to be this way

dnolen18:12:26

since the details of parsers between clients and servers may not necessarily be the same at all

peeja18:12:30

Yeah, that makes sense. I had it in my head that there would always be something reasonable to call :state. (And maybe there almost always is, but it doesn't mean the parser has to care.)

peeja18:12:18

I'm finally getting why it's just called env, and now it's a lot clearer. simple_smile

dnolen18:12:46

right on the server you may not even have :state

dnolen18:12:51

only :db-conn or whatever

dnolen18:12:04

or :http-client etc.

peeja18:12:45

Yeah, I was imagining you'd have to call that your :state, but I suppose there's also no reason you'd have to have only one, right?

peeja18:12:51

You might be stuck with multiple data sources

robert-stuttaford18:12:27

quite an elegant design

dnolen18:12:55

@peeja: on the server you are full control of how you construct env

dnolen18:12:08

on the client we do construct it on your behalf

dnolen18:12:28

but :state can easily be a DataScript DB so I haven’t seen a real need to make it more flexible.

peeja18:12:12

Ah, right, that makes sense. And it's the reconciler that builds it for you?

peeja18:12:19

Okay, thanks for the help!

peeja18:12:20

(Oh, I see, it's to-env that defines that)

heeton19:12:42

Hi all. Complete noob question here, I’ve got a bit of experience with Clojure but trying to pick up Om. Can’t make the jump from the basic getting started to an actual SPA with pages, URL params etc. Can’t even seem to work out how best to get to setting params on components. Could someone point me to some examples of this?

wilkerlucio19:12:45

@heeton I suggest you follow the Om.next tutorials at the wiki: https://github.com/omcljs/om/wiki

heeton19:12:49

For instance, what’s a good way to implement different components / different pages?

heeton19:12:17

@wilkerlucio: I have done the wiki ones, I’ve browsed through the others listed in the footnotes but can’t seem to see anything that deals with this

wilkerlucio19:12:54

@heeton: people are still figuring out ways for routing, no standard at this point, you may wanna go easy at first, since you have done the wiki pages check the om-tutorial from tony kay, it can give you more experience with om.next: https://awkay.github.io/om-tutorial/

maackle19:12:30

Wow, a lot of activity in the last 8 hours! @artemyarulin, responding to your response from 8 hours ago about re-rendering after transact!...I did take that bit of wisdom into account, and it doesn't help in that case. I'm also transact!ing on the reconciler itself, not some child component, so I would think that would render the whole shebang. I have a feeling the problem is in the asynchronous nature of the mutation, so the transaction completes before the novelty is merged in.

maackle19:12:10

Here's a fresh question for everyone: When doing a mutation involving an async XHR, do I have to ensure that the transaction finishes after the novelty is merged in? If so, how? If not, why else could it be in my case that the novelty-merging callback does not trigger a re-render?

maackle19:12:47

My transact! call looks like this:

clj
(om/transact! core/reconciler `[(people/add ~verified-person)
                                 ;; these reads are basically all the queries for the entire app
                                 {:widget/person-list [{:list/people [:natal-chart]}]} 
                                 {:widget/aspect-table [{:list/people [:natal-chart]}]}])

heeton19:12:11

@wilkerlucio: thanks. Could you suggest some current options? Are people plugging in existing routing libs, or rolling their own?

tony.kay19:12:19

@maackle: You should not, in general, transact on the reconciler. There is no "whole shebang" re-render. The keywords (and you need only supply keywords) mentioned after a transact will cause re-render of any component that has those in their query.

tony.kay19:12:53

There is no async (reasoning) anywhere except your send, if you are doing it right.

maackle19:12:43

OK, even transacting on the local component (`this` in this case) leads to the same behavior

maackle19:12:57

So I wonder what I'm misusing here

maackle19:12:29

My send is written pretty much like the tutorial here https://github.com/omcljs/om/wiki/Remote-Synchronization-Tutorial

maackle19:12:15

it pushes data and callbacks onto a channel, which gets taken by a go-loop that makes XHRs and calls the callback on the novelty

tony.kay19:12:21

So here is the sequence of events: 1. (people/add) will cause an invocation of mutate for local (target = nil). If you return an action thunk, it will run right then against local app state 2. (people/add) will cause an invocation of mutate for each remote (target = remote). IF you return :remote true, then that will get queued 3. The local reads will happen 4. Send will run

maackle19:12:15

Ahh I didn't think about the remote invocation. But I also haven't seen any examples of that so far.

maackle19:12:55

Does the mutate function need to return {:value ... :remote-key foo} similar to reads? And if so, what is foo here? another ast?

tony.kay19:12:49

Have you read the om-tutorial in community docs? It explains a lot of the minutia

maackle19:12:01

But wait. I'm not actually doing a remote mutation. I just want the mutation to trigger a remote fetch from my :read parsing

wilkerlucio19:12:15

@heeton: I saw people talking about routing but I haven't tried to do tackle it myself, sorry but I can't remember a link with a reference now

maackle19:12:02

@tony.kay: I've read a few, not sure which you're referring to. You mean on the github wiki?

heeton19:12:26

@wilkerlucio: No worries, thanks anyway

tony.kay19:12:20

Yes, it is referenced from the wiki

maackle19:12:22

yes I have read that one

tony.kay19:12:44

ok, guess I have not beefed that up enough yet simple_smile

maackle19:12:57

can't say I fully understand everything, but at least I think my use case hasn't shown up there

maackle19:12:17

well, I've learned much of what I know from it, so thanks simple_smile

anmonteiro19:12:24

@heeton @wilkerlucio: I've put together a lein template that I've linked before (https://github.com/anmonteiro/aemette) which brings a basic web app with routing included. It couples together some ideas that I've been using in my own apps

tony.kay19:12:21

@maackle: OK, so general rules for transact!: 1. Transact on a component. The one that "owns" the thing being changed...if a child needs to trigger the action, then send a callback to do so from the owner using om/computed 2. Follow mutations with keyword property names (not queries). (the keywords will get transformed into appropriate UI queries)

tony.kay19:12:58

Your read/mutate infrastructure will then have to decide what to do. If you want remote reads, then your code will have to say so. If you want local stuff, then they have to do it.

tony.kay20:12:21

Put tracing code into your read/mutate, and watch what happens

tony.kay20:12:42

be sure to dump target so you can tell the local vs. remote pass

maackle20:12:37

What do you mean by 2. Follow mutations with keyword property names?

tony.kay20:12:16

(transact '[(f) :a :b])

maackle20:12:32

gotcha, and in my example I used queries

tony.kay20:12:11

thing is, the queries will get parsed but won't have the desired re-render effect

maackle20:12:37

Thanks for all this. I'll take it and run with it. I think the issue might be that I'm expecting too much of a particular remote read. It sounds like I have to abstract out the XHR stuff from that read and put it directly into this mutate, rather than trying to trigger the query that causes the remote read after the mutation

tony.kay20:12:33

read/mutate MUST both be side-effect free. No network code goes there ever

tony.kay20:12:37

it all goes in send

tony.kay20:12:34

"triggering" remote read is you returning {:remote true} or {:remote some-ast}

tony.kay20:12:53

which causes that particular query (sub)expression to show up in send

tony.kay20:12:51

(defn my-read [env k p] (when something {:remote true }))

tony.kay20:12:01

same with mutate

maackle20:12:03

yes they are side-effect free, but my read causes a remote query like [(:action {:params ...})] which gets picked up by send and does XHR stuff

maackle20:12:51

so I think I need to make sure that happens directly with the mutation, i.e. can't rely on the read getting triggered again after the mutation

maackle20:12:04

(even though it does get triggered and does the XHR and state update, just doesn't cause rerender)

tony.kay20:12:11

um, that does not sound right

tony.kay20:12:16

try the keywords fix first

tony.kay20:12:25

Om queues those top-level keywords for re-render

tony.kay20:12:29

you are using queries

tony.kay20:12:21

if you want the queries in there as well, fine

maackle20:12:22

OK, that alone wasn't enough

tony.kay20:12:39

By putting the keywords in there, you tell Om to queue re-renders of all components that query for those keywords. That happens during the default merge (which you trigger via thecallback in send)

tony.kay20:12:49

unless you overrode the merge

tony.kay20:12:54

then you might be screwed simple_smile

maackle20:12:32

yeah I did simple_smile

tony.kay20:12:38

ok to override merge-tree and migrate

tony.kay20:12:57

you may want to fix that...you almost certainly want to override merge-tree

maackle20:12:24

actually that's what I did

maackle20:12:27

not merge itself

tony.kay20:12:32

I would not recommend messing with merge unless you really know what you are doing

maackle20:12:55

(om/reconciler {... :merge-tree merj})

maackle20:12:07

(defn merj
  [a b]
  (if (and (map? a) (map? b))
    (merge-with merj a b)
    b))

maackle20:12:23

(naive I know, but it works so far)

tony.kay20:12:26

yeah, that won't affect rendering.

maackle20:12:40

that's the confusing part: the callback definitely does merge in the proper state. but rerendering doesn't happen.

maackle20:12:31

I tried console.logging the app-atom every second with js/setInterval, and it definitely updates after the callback runs. just no rerender. if I cause a rerender through some other interaction, all the new stuff shows up

tony.kay20:12:00

hm...hold on, reading some source...I'm confused about what merge is doing

tony.kay20:12:23

The things that get queued on merge are: (into [] (remove symbol?) (keys res))

tony.kay20:12:47

where res is the delta (that you sent into the callback, if I'm reading it right)

tony.kay20:12:31

(by queued, I mean for re-render)

tony.kay20:12:45

that means, it would seem to me, any top-level (sub)item that came back with data (and all children). Result looks like {:x {...} :y {...}} would queue render for :x and :y

maackle20:12:45

I'm glad you got me looking at the source too, I think I may have to trace the internals to see what's really going on for my case

tony.kay20:12:50

and transact! does this for rerender list: (p/queue! r (into q (remove symbol?) (keys v)))

maackle20:12:54

there must be something slightly different going on here. There is another case where the same callback gets called on some novelty and the rerender does happen after. but with this mutation, no

tony.kay20:12:51

where v is the result of the local parse

maackle20:12:31

result of the local parse...I'm not getting what that is here

tony.kay20:12:37

Yeah, trace it down. Them's the specifics I know, and I feel like I just understood them better myself simple_smile

tony.kay20:12:55

a call of your parser on the transaction

tony.kay20:12:04

(parser env tx)

maackle20:12:08

yeah that seems like a really important difference to look at, those two queue!s

tony.kay20:12:15

where tx is what you send transact!

tony.kay20:12:48

the two queues are identical...they both take the keys from the response (top level) and put them in for a re-render

tony.kay20:12:15

oh, missed on step

tony.kay20:12:09

it also queues the component that ran transact, as well as the ident of that component, if it has one

maackle20:12:52

well that's the difference then: in the case of transact! it queues these things immediately, in the case of read it queues after the callback merges novelty, right?

tony.kay20:12:50

um...that is all jumbled up, I think

tony.kay20:12:27

transact immediately causes re-render, and queues any sends that your read/mutate functions say are remote

tony.kay20:12:42

your network code runs

tony.kay20:12:04

when your network code chooses to call the callback (which is merge), then another re-render happens based on the novelty

tony.kay20:12:42

if your response contains no top-level keywords as keys = no re-render

tony.kay20:12:03

oh, you are parameterizing the prop read, no?

tony.kay20:12:21

you said earlier: [(:prop {params 1})]

tony.kay20:12:28

which is your response key

tony.kay20:12:31

which is not a keyword

maackle20:12:36

OK so it could be that my novelty does not reference any actual components, only ident tables...

tony.kay20:12:55

:prop in that example is inside of a list

tony.kay20:12:33

{ (:prop {params 1}) value }

tony.kay20:12:35

is the response

tony.kay20:12:45

there is no top-level keyword for keys to get out of that

tony.kay20:12:18

so, I'd probably consider that a bug, unless David has some reason for not re-rendering something parameterized, an I could not imagine that

tony.kay20:12:39

drop the params, and I bet it works

maackle20:12:07

but if we're talking about the queue! after merge, it takes the state delta, not a query

tony.kay20:12:18

I just showed you a state delta

tony.kay20:12:36

(:prop { params 1 }) is the KEY to a MAP

maackle20:12:55

I build my delta manually, it doesn't match the query

tony.kay20:12:05

well, that could be a problem

tony.kay20:12:26

and the fact that the params are there are probably hanlded at the rendering stage, now that I think of it...it is still a key

tony.kay20:12:38

just not a keyword

tony.kay20:12:29

I need to head out. hopefully that gets you closer. Inspect what you are making, it should easier to figure out now

maackle20:12:33

I think we both just had distinct unrelated insights simple_smile

maackle20:12:43

Yes thank you so much! I have a lot more to investigate now

tony.kay20:12:59

@maackle: FYI: It does not appear that parameterized top-level queries will re-render properly.

tony.kay20:12:14

it will not find them in the index, I don't think

peeja21:12:13

Is there an architectural reason why read and mutate are different functions? Given one receives keyword keys and the other receives symbol keys, it seems like they could be a single function.

adammiller21:12:09

@peeja: those functions are provided…they have same signature so you technically could but they have different expectations in what is returned. I would not conflate the two. Typical approach is to utilize multimethods for each.

mudphone21:12:23

When not passing an atom to om/reconciler (as state), should I be doing something similar to the defonce on the atom that I used to pass (when I wasn’t using the indexer)?

mudphone21:12:44

The reason I’m asking is that the entire app state is refreshing when I make a trivial update to a component. The state is reset too when I check @reconciler.

mudphone21:12:05

(def reconciler
  (om/reconciler
    {:state app-state
     :parser (om/parser {:read read
                         :mutate mutate})}))

(om/add-root! reconciler
  sentence-view/SentenceView (gdom/getElement "app”))

mudphone21:12:02

That’s how I’m setting up my root and reconciler. I used to defonce my app-state before I started using the indexer. But, I don’t do that any more.

mudphone21:12:32

Didn’t think it was necessary, since it is passed to the reconciler as a plain old map.

anmonteiro22:12:57

@mudphone: not quite sure what your problem is

anmonteiro22:12:15

@mudphone: are you using figwheel?

mudphone22:12:41

I’m looking for some figwheel with Om Next demo projects out there…

anmonteiro22:12:39

So the problem is in reloading, not in the reconciler (not sure what you want to mean by indexer, I think you're mistakingly referring to it as reconciler)

mudphone22:12:13

sorry, I mean, I switched to passing a map to the reconciler as state (instead of an atom), in order to get it to index my tables?

mudphone22:12:33

I guess I made that term up (indexer) simple_smile

anmonteiro22:12:43

Indexer exists

anmonteiro22:12:15

But I don't think your problem is related to that switch

adammiller22:12:48

@mudphone: several good projects out there. Even the quickstarts in the om.next documentation set you up with figwheel.

anmonteiro22:12:56

@mudphone: Anyhow, have you read through figwheel's section on writing reloadable code?

mudphone22:12:09

@adammiller: yes, I started there… I’m just wondering if I’m missing something (again), so looking for more examples… thanks for the tip though… I think maybe I should probably retrace my steps, starting there might help.

anmonteiro22:12:35

Cool, I'd start by defonceing the reconciler

mudphone22:12:51

@anmonteiro: that’s simple enough

mudphone22:12:29

@adammiller: thanks, just downloaded it a minute ago, but haven’t taken a look yet… also going to dive into the guts of @tony.kay ’s devcards tutorial

anmonteiro22:12:52

@mudphone: also not sure if something should be done about add-root!

mudphone22:12:56

@anmonteiro: still resets the app state… I must be doing something basic wrong… I think I need to reduce this to a minimal example, I’ve been throwing things together as I learn them

mudphone22:12:50

hmm in the kanban demo they place the` add-root` into a run fn

donmullen22:12:20

@mudphone @adammiller - not that the kanban demo uses boot instead of lein/figwheel I believe.

anmonteiro22:12:04

@mudphone: I'd try wrapping the add-root! call in a function and call it on demand

anmonteiro22:12:28

(Not on figwheel reload)

mudphone22:12:35

@anmonteiro: figwheel hangs, I’m guessing because I don’t get a chance to add-root

mudphone22:12:42

and the browser never connects

mudphone22:12:03

The last message from figwheel is "Prompt will show when Figwheel connects to your application"

mudphone22:12:13

let me try this again

jannis22:12:41

Hmm, I wonder... does the fact that so many seem to find the kanban demo useful even without any source comments or documentation prove that Om's goal of leading to predictable, universally understandable program code is being met? 😉

mudphone22:12:43

I was starting figwheel and the repl simultaneously… I’ll do it in steps

a.espolov22:12:18

Guys, how to set query for root component, if this component use sub-query?

a.espolov22:12:45

set->query call for root component or component child?

mudphone22:12:25

@anmonteiro: looks like I can’t put the add-root in an fn, because figwheel needs it to run on startup

adammiller22:12:34

@jannis Om’s goal along with the way you wrote and structured the code…it is very easy to read and understand so thanks for that!

mudphone22:12:48

@anmonteiro: but, I can put it in a defonce… and I still have my original problem of the state refreshing

mudphone22:12:25

@anmonteiro: oh wait, I mis-spoke, I can get figwheel to connect before running a run fn with add-root in it

mudphone22:12:32

@anmonteiro: going to try putting my core/run fn into the figwheel { :on-jsload "example.core/reload-hook" }

a.espolov22:12:31

jannis: You do not ever use IQueryParams and IQuery?

jannis22:12:53

IQuery? All the time!

jannis22:12:13

It would be no fun without queries 😉

a.espolov22:12:01

jannis: component A use query from component B as sub-query

a.espolov22:12:58

call the set-query for component B throwed "No queries exist for component path (front-end. core/A front-end/B)"

jannis22:12:59

Heavily used everywhere as well

a.espolov22:12:58

call the set-query for A component, query is executed against the default params

jannis22:12:26

Are you calling set-query against A or an instance of A (`this` for example)?

adammiller22:12:47

I was having issues comprehending how tempid migration works so I created a very basic app in case anyone else might be interested that shows how it works https://github.com/akmiller78/tut-omnext-tempids

a.espolov23:12:31

@jannis: set-query against A

jannis23:12:58

@a.espolov: What does the query of the component look like afterwards?

a.espolov23:12:08

@jannis: full example above, complete example above, next query equal first query use paging params 0 5

jannis23:12:25

@a.espolov: You're aware that you're not parameterizing the query in B correctly, yeah?

jannis23:12:42

I suppose since you're taking it apart in A, parameterization may not be what you're after.

a.espolov23:12:33

conclusion is simple in this situation, it is better not to use the sub query?

jannis23:12:52

But you're setting the query of A and try to update the query params for B? That's never going to work.

a.espolov23:12:02

I first tried to install query to B

a.espolov23:12:14

but received error

a.espolov23:12:15

{:message "No queries exist for component path (front-end.core/A front-end.core/B)", :data {:type :om.next/no-queries}}

jannis23:12:24

If you want to parameterize the query of B, one thing you'll have to look into is the parameterization syntax. Embedded in a component query that's [... (<query expr> <param map>) ...]. The way you're doing it in your example is simply broken. You're also picking the query of B apart in A which is wrong.

a.espolov23:12:45

@jannis: (om/get-query B) returned structure data which does not understand backend parser

jannis23:12:44

I'm sorry, that doesn't make any sense to me.

a.espolov23:12:05

I found the other way to do so is to sub-query Therefore, this code appears ' [( (first q-inspections) (last q-inspections))]

jannis23:12:30

I'd be surprised if that worked. Certainly setting :params in A where they are originally defined in B seems wrong.

jannis23:12:58

I would try to get parameterization work in a single component first and if that works, move it down into the sub-component. Picking the sub-query apart with first and last like you do is not valid AFAIK, so don't do it. Include the full sub-query as-is.

tony.kay23:12:37

Are we supposed to be able to join across a top-level link. E.g. [ { [:current-user '_] [:things (om/get-query Thing)] }]? I can get it to render properly, but it breaks re-render if I try to state change on Thing: "No queries exist...". Seems like the indexer cannot find the query.

a.espolov23:12:42

@jannis: :abc/one key is local data, :inspection.list/outlet remote get data

a.espolov23:12:52

how to make sub-query?)