Fork me on GitHub
#om
<
2015-10-25
>
tyler02:10:33

Is there something special that needs to be done when performing delete mutations? I have a list of items in my state and then I try to remove them all using a swap! mutation in the parser. The action succeeds and the state is updated; however, the dom doesn’t re-render until I perform another non-delete action (like adding a new entry)

tyler02:10:34

Or I suppose more specifically is there a way to trigger a re-rendering. I tried using om/schedule-render! however it doesn’t seem to re-render until I interact with something that changes component local state (like a text box)

dnolen02:10:44

You add reads to your transaction

tyler02:10:46

Boom solved. Can’t thank you enough @dnolen

bbss05:10:53

@sander, I had the same problem on one of the projects I was using for exploring om next. Starting a new one didn't have the problem indeed. Did you find out how to fix it?

sander07:10:05

@bbss no. one circumstance was that i was not working in the :main ns i specified in figwheel.clj, maybe has to do with that somehow

sander07:10:15

(and in the main ns, om.next was also being used)

bbss08:10:17

@sander, I was so don't think that's it.

thomasdeutsch08:10:47

@dnolen thanks for the quick fix. with the same code, i now get this error: Uncaught Error: No method in multimethod 'scheduler.main.core/read' for dispatch value: :item-two/title -> but why is om.next looking for a reader for this subquery? https://gist.github.com/ThomasDeutsch/ec80942bca3270013032

thomasdeutsch09:10:23

@dnolen sorry. i have seen your message right now - will check my code.

thomasdeutsch11:10:02

Still having a problem. After selecting another item, the old item will still get rendered. https://gist.github.com/ThomasDeutsch/141ba1d7c8691418869c.

dnolen12:10:57

@thomasdeutsch: I do not understand what the code you have written is trying to do

dnolen12:10:46

I no longer see anything that would let you switch in Root

thomasdeutsch12:10:50

i tried to do this as simple as possible. i use two readers. Based on the result of those two readers, item-one or item-two will get rendered. I could use only one reader function, but can not see why this would be a better option.

dnolen12:10:48

what I mean is I don’t see how the program you’ve gisted can work

dnolen12:10:05

the approach in the earlier gist was correct

dnolen12:10:29

the bug was that you were including a map with :one or :two in the result

dnolen12:10:44

but that was the union query - union queries don’t actually “exist"

dnolen12:10:56

one of them will be chosen, but which one was chosen gets erased

thomasdeutsch13:10:32

Not that easy for me simple_smile I can change the earlier gist, so that the result will not construct a map with the :one and :two keys. But then, it seems that the query in Root is not correct - it will call the reader with {:one [...] :two [...]} as selector but after a mutation, it will get called without the keys and only with one subquery. So, i changed the query to this: [{:selected-item [(om/get-query ItemOne) (om/get-query ItemTwo)]}]. now i get this error: "No queries exist for component path (scheduler.main.core/Root scheduler.main.core/ItemOne)" gist for the second case: https://gist.github.com/ThomasDeutsch/da3a1b8d88af2507e04f

thomasdeutsch13:10:19

@dnolen: and this is for the first case (query: ` [{:selected-item {:one (om/get-query ItemOne) :two (om/get-query ItemTwo)}}] ` ) https://gist.github.com/ThomasDeutsch/c8b0226de255b6b884d3

dnolen13:10:17

you are talking about too many things at the same time

dnolen13:10:55

I only have mental capacity to think about one thing at a time simple_smile

dnolen13:10:17

ignoring everything you said except the first thing

dnolen13:10:34

what is the problem with the reader getting the union query?

dnolen13:10:52

this is a necessity if you were going to for example query Datomic and needed to know which selector to apply

dnolen13:10:03

please do not mention any of the other things you’ve brought up

dnolen13:10:09

only this first problem you encountered

thomasdeutsch13:10:19

the problem is: after a mutation, the reader will not get the union query. it will receive only one subquery from one of the items.

dnolen13:10:43

that doesn’t mean anything to me yet

dnolen13:10:59

how is it possible that the reader won’t get the full query after a mutation?

thomasdeutsch13:10:02

i think it is because the query in my Root is not correct. It is this one: [{:reader-fn {:key [subquery] :two [subquery]}].

dnolen13:10:34

that’s your union bit

dnolen13:10:25

the problem I think is you’re invoking the switch from somewhere where the switch doesn’t actually happen.

dnolen13:10:34

it happens at the root

dnolen13:10:50

but the child components are triggering the transaction (which is a bad idea in general)

dnolen13:10:16

so when they go to re-rendering they don’t actually know what’s going on

dnolen13:10:24

why? because they don’t know what the union is

dnolen13:10:41

only the root does

dnolen13:10:52

so only the root should ever trigger a switch

thomasdeutsch14:10:36

thank you for this feedback! I did not now that it makes a difference what component will trigger the mutation, since everything will simply result in a db(state) change and i can trigger re-reading with the :value param of the return map. i think it is time for me to get into the source a bit more.

thomasdeutsch14:10:07

it solved the problem ...

dnolen14:10:41

the fundamental issue is that children deep in the tree don’t have all the information

dnolen14:10:47

and this is a good thing

dnolen14:10:55

you need to move control back to where it belongs

thomasdeutsch14:10:10

yes... ... it makes sense. it is like the standard react philosophy.

dnolen14:10:36

yes randomly invoking transactions anywhere really just can’t be made to work

dnolen14:10:51

it would be nice if we could prevent this case, but’s of course impossible to know what a transactions means

dnolen14:10:07

still you uncovered a really crazy bug yesterday

dnolen14:10:16

that was a head scratcher for me for a bit

thomasdeutsch14:10:42

i am so happy that i am able to help a tiny bit

dnolen14:10:55

all this feedback is much appreciated

dnolen14:10:07

everyone is going to encounter these cases so it’s just more fodder for documentation

thomasdeutsch14:10:11

@dnolen: last question for today: if transactions can only be called from the root - do i pass the callbacks down the tree?

dnolen14:10:03

the point is not that transaction can only be run from the root

dnolen14:10:16

the point is that you run them where they make sense

dnolen14:10:29

and yes pass callbacks just like React

dnolen15:10:45

ok property based testing of UI is pretty freaking sick

bbss15:10:04

Where can I read more about it?

dnolen15:10:13

writing up a new tutorial

akiva15:10:12

In the Components, Identity & Normalization tutorial, in the section Studying Identity & Normalization/Adding Reads, the REPL code is producing a data structure where “Mary” loses her :age key. Is this expected behavior?

mhuebert15:10:41

should it be possible to return records from an om/next query? it looks like they are coerced to maps (https://github.com/omcljs/om/blob/master/src/main/om/next.cljs#L53)

dnolen16:10:53

@akiva you don’t necessarily want to pass information that doesn’t belong

akiva16:10:17

Oh I see.

akiva16:10:27

The Person only wants :name and :points.

dnolen16:10:35

@mhuebert: there’s no support for records at the moment

dnolen16:10:51

feel free to file an issue - low priority for me probably until we get into the beta phase

akiva16:10:13

The more I mess with this, the more impressive it gets.

dnolen16:10:59

we’re just getting warmed up

dnolen16:10:22

already passed the threshold where I wouldn’t use anything else

dnolen16:10:52

no other thing I’ve used solves all these various problems I’ve encountered in UI programming over the last 10 years in one system

akiva16:10:47

That’s great. I’ve really liked your transparency with the coding; and how giddy you get over it is pretty infectious.

paulb16:10:00

Has anyone tried Devcards with Om Next? I am trying to understand how testing components with state works

dnolen16:10:17

just deployed 1.0.0-alpha9

dnolen16:10:27

will be needed for the property based tutorial I’m working on

dot_treo16:10:02

@dnolen, the remote synchronization tutorial seems to be cut short. I'm probably missing something there and it was just meant to show how http caching can be achieved and the other parts will follow?

dnolen16:10:14

@dot_treo: it is cut short

dnolen16:10:25

I’ll get to it when I get to it

dnolen16:10:35

it’s incredible how much documentation 1200 lines of Clojure needs simple_smile

dot_treo16:10:01

Good ideas tend to be hard to explain simple_smile

dnolen16:10:23

yeah I wrote that up just to talk about HTTP caching

dnolen16:10:38

which everybody incorrectly assumed wasn’t recoverable in a Relay/Falcor model

dnolen16:10:16

and as I said on Twitter it just blows open the door on what backend you can use

dnolen16:10:57

even if you cannot write super fast SQL queries you should still be able to write a lot of interesting applications without venturing from more standard backend choices thanks to HTTP caching

dot_treo16:10:18

and writing fast sql queries isn't all that hard

dnolen16:10:43

it isn’t, but you can even use a naive ORM

dnolen16:10:00

the point is that a much wider array of developers can leverage the approach without fear of messing it up

jannis17:10:34

If a component's ident and query refer to a particular "object", e.g. [:person/by-name "John"], can you safely pass in (some-component {:person john :activate-fn #(...) :some-other-callback #(...)}) or do you always have to pass in (some-component john) from the parent? I was wondering, since one may want to pass in callbacks etc. in addition to the "object" itself.

jannis18:10:19

I guess ident would have to be implemented like this? (ident [this props] [:person/by-name (-> props :person :name)])

jannis18:10:25

Actually, no, it only works with (ident [this props] [:person/by-name (:name props)]). Perhaps ident gets passed the query result during normalization? If I then want to pass in additional properties (like callbacks) in render, (om/get-ident this) will no longer work, since query result and properties don't have the same structure. Hmm...

jannis18:10:15

@dnolen: Any chance to ensure (om/get-ident this) passes the same data to (ident this ...) as during normalization, even if the props passed to a component have a different structure?

dnolen18:10:46

you shouldn’t be doing that

dnolen18:10:53

how ident is computed should always be the same

jannis18:10:32

Shouldn't be doing what?

dnolen18:10:54

passing in different structure to compute ident doesn’t make any sense

dnolen18:10:06

it has to be the same

jannis19:10:19

@dnolen: Sure, I understand that. Is it a valid idea though to have a query like [:name], an ident like [:person/by-name (:name props)] but pass in more than just the person to the component via its props?

dnolen19:10:10

@jannis I just don’t know what that means

jannis19:10:45

(component person) vs. (component {:person person :some-fn #(... perform a transaction or something ...)}). I'm assuming the latter is incorrect given the above query and ident?

jannis19:10:30

(And given a (defui Component ...) and (def component (om/factory Component)) of course...)

jannis19:10:54

I'm only asking because I'm wondering how to pass in more than just the query result to components. Like callbacks for instance.

dnolen19:10:40

still not making any sense to me

dnolen19:10:52

the first thing you saying here doesn’t make any sense

dnolen19:10:58

you can never pass anything that isn’t in the same shape as the query (fixed my statement here)

dnolen19:10:13

@jannis but perhaps you’re asking it ok to add some more fields … yes

dot_treo19:10:17

How would you pass in callbacks to a component that defines a query? The callbacks are not part of the state after all?

dnolen19:10:27

but changing the structure of the thing is a big no no

jannis19:10:34

So (component (assoc person :some-fn #(...))) would be fine.

jannis19:10:36

It strikes me as a little odd but I guess I'm ok with it.

dnolen19:10:43

what you were suggesting is far more odd

dnolen19:10:54

the subcomponent which you don’t control requested something

dnolen19:10:03

then you went and broke the contract

jannis19:10:38

Well, it queried data but that doesn't mean it doesn't also expect a callback to be passed in. It can't really include a query for a callback, can it?

jannis19:10:57

But perhaps by "requested something" you refer to the structure and fields of the props passed in?

dnolen19:10:00

the callback part isn’t relevant

dnolen19:10:04

you reshape the data

dnolen19:10:07

you break the query

dnolen19:10:55

everything I’ve written so far explicitly never reshapes at components

jannis19:10:27

Yes. But what you've written in the tutorials doesn't ever pass in a callback (e.g. to perform a transaction in the parent component rather than the component itself), so I was looking for a way to achieve that. Turns out my attempt is wrong - I'm ok with that now that I know better.

dnolen19:10:43

@jannis I’m just pointing out that people often want to talk about two problems at the same time. I’m just trying to guide people asking only question at a time.

dnolen19:10:18

So 1) it’s never OK to reshape data in components. 2) adding more fields is fine (a component can’t ask for callback in their queries anyway)

jannis19:10:52

Yeah, I noticed that's a recurring pattern today. (People asking multiple questions.) 😉

jannis19:10:26

It's hard to ask questions the right way when uncertain though.

jannis19:10:43

Thanks, 1) and 2) make things perfectly clear. simple_smile

dnolen19:10:02

@jannis still this isn’t the first time someone has asked this

dnolen19:10:17

so I think this suggests we should do basic props validation

dnolen19:10:38

but will need to think about that, validation tends bring undesirable things when you want things to be more flexible

tony.kay19:10:15

@dnolen: FYI your new doc on property-based testing refers to a snapshot version in the dependencies list.

thomasdeutsch20:10:32

1. I think it should be mentioned in the basic tutorial, that we should pass callbacks down from the point where the queries get constructed (the read functions are). i was using a flux architecture beforehand - and able to dispatch any transformation / action from any sub component, without using callbacks.

dnolen20:10:55

@thomasdeutsch: the problem is explaining something people aren’t ready to understand yet

dnolen20:10:07

I’m pretty anti putting stuff like that in basic tutorials

dnolen20:10:42

the only reason is worked in flux is because flux doesn’t really have a discipline about how reads or how coordination happens

dnolen20:10:55

so it’s free-for-all, so of course it works

dnolen20:10:20

also not interested in any comparisons really to anything else

dnolen20:10:50

that said, it needs to go somewhere or be addressed somehow

dnolen20:10:33

but at the moment I don’t have any good ideas about

dnolen20:10:41

and deciding anything without a good idea is a waste of time

dnolen20:10:05

but in case anybody else wants to come up with some better suggestions … there are a couple of options worth hammock'ing

dnolen20:10:28

1) we could force subcomponents under a union to automatically lift their transaction to the union point

dnolen20:10:34

perf implications here

dnolen20:10:53

2) declarative discipline about what components can run what transactions

thomasdeutsch20:10:19

will do om.cursor 2.0 (transaction-cursors) (kidding)

dnolen20:10:51

3) remove components from being implicitly re-rendered if they kicked off the transaction

dnolen20:10:15

@thomasdeutsch: ^ this is kind of the source of the problem

dnolen20:10:38

if we never implicitly re-rendered you would immediately have to think about where you want the change to occur

dnolen20:10:22

but that means the most common case has to be made explicit

tony.kay20:10:13

OK, I’ll sound like the dense one…. On (3): why do we care if the component that kicked off the transaction gets implicitly re-rendered? Isn’t it cheap to do so?

dnolen20:10:21

I’m saying 3) is how it work today

dnolen20:10:41

normally if a component transacts we want it to be re-rendered

dnolen20:10:01

but @thomasdeutsch’s case bring up a subtlety around unions

dnolen20:10:25

the children of a parent with union don’t see the union - they have their own specific subquery

dnolen20:10:25

if a child transacts, when parse executes it’s query (which is a more specific one of the parent one) you won’t see the same query information

dnolen21:10:02

this is a problem if you are using the query to make decisions which @thomasdeutsch was

dnolen21:10:23

will probably want to hear more cases like this before deciding whether this even a problem

tony.kay21:10:39

I see. Thanks for the extra detail.

tony.kay21:10:53

I was under the impression that the entire UI tree query re-ran on each render, but that the list of things queued was used to optimize which things in the UI returned true/false from shouldComponentUpdate

dnolen21:10:47

right so that isn’t actually true

tony.kay21:10:01

yeah, I see that now simple_smile

dnolen21:10:02

children only record the “focused” query

dnolen21:10:39

this is just simple pruning to avoid recomputations not on the path to child

dnolen21:10:58

[:foo/bar :bar/baz {:woz/goz [:id :title]}]

dnolen21:10:05

so if a child is under :woz/goz

dnolen21:10:16

the query it uses drop those other keys

tony.kay21:10:14

When I read the code (and misunderstood it) I assumed since state it was in RAM cache, you were just assuming JS was “fast enough” for reasonable sized UIs. In a way I’m glad I’m wrong about that, but now I understand what you’ve been talking about with callbacks.

dnolen21:10:13

right there are definitely optimizations present for larger applications

dnolen21:10:30

you wouldn’t want to rebuild data for subcomponents that aren’t on the re-rendering path.

tony.kay21:10:53

Agreed…but that was at the root of a lot of my misunderstanding, because I had read the code for re-render, and missed the optimization. Thus I was continually puzzled as to why a transaction couldn’t just run anywhere.

dnolen21:10:03

and to be clear exactly what part of the tree we rebuild is definitely an implementation detail

dnolen21:10:27

we may figure out a way in the future to only rebuild a specific component’s data and avoid re-running queries

thomasdeutsch21:10:13

in some cases i used the datascript transaction-log to see what entites/attributes changed and updated accordingly.

abp22:10:08

@dnolen there's a missing closing paren at the defmethod form of read snippet in the property based testing tutorial

dnolen22:10:03

Feel free to fix. Not at a computer.

abp22:10:44

ok will do

abp23:10:08

done, i also have some problems evaluating the property based tests, has anyone else tried?

abp23:10:00

ok nvm, seems like some caching got in the way