Fork me on GitHub
#om
<
2015-11-07
>
joshfrench00:11:15

is this the right syntax for parameterizing a join? [({:dashboard/posts ~(om/get-query Post)} {:foo "bar"})]

joshfrench00:11:49

without params, everything works fine; when i add them, any transaction results in “No queries exist…”

joshfrench00:11:08

i’m not even looking at the params in the parser

dnolen00:11:52

@joshfrench: yeah could be a bug, something minimal would be nice and I can make a test out of it

simonb00:11:37

@jannis: what does :poll/questions do in that transact expression? Seems not to be used. Sorry about time zone delay.

jannis01:11:12

@simonb Its the way the app expresses which queries it wants to read again after the transaction.

jannis01:11:20

If you transact from a component the queries for that component are rerun. But if the transaction affects keys that are not part of the component's query, you have to state that you want to reread them (or a subset of them).

leontalbot01:11:24

@jannis simple_smile This is really good to know! Thanks

jannis01:11:00

Which is the case in your example - Question doesn't query for :poll/questions

jannis01:11:10

You could pass a callback down from the root and transact in the root, in which case you could omit :poll/questions in the transact! call, because it's part of the root query.

leontalbot01:11:23

@jannis yes, but I didn't know why to put it there. Was just trying everything I could to make this work, I must confess

simonb01:11:14

@jannis: thanks. Is this in the docs?

leontalbot01:11:49

@jannis would you mind sharing a callback example (some code)

simonb01:11:10

+1 Would love to see callback example too

jannis01:11:39

Yeah, it's not covered in detail on the official docs page.

leontalbot01:11:29

Mutations should return a query expression for the :value. This query expression is a convenience that communicates what read operations should be followed by a mutation. Mutations can easily change multiple aspects of the application (think Facebook "Add Friend"). Adding the option :value result helps users identify stale keys which should be re-read. The guiding principle here shares common goals with HATEOAS.

jannis01:11:01

That documents the :value returned by mutate methods. That has no effect.

jannis01:11:00

Callback examples here: all transactions here pass in no keys because App includes all affected queries already. https://github.com/Jannis/om-next-kanban-demo/blob/master/src/kanban/app.cljs

leontalbot01:11:39

Aren't they linked?

(om/transact! this 
  `[(selections/update-radio {:name ~id :value ~new-value :type ~type}) 
  :poll/questions])

leontalbot01:11:33

(defmethod mutate 'selections/update-radio
  [{:keys [state]} key {:keys [name value type]}]
  {:value [:poll/questions]
   :action ...

jannis01:11:46

:value is just a hint for developers. It states "these keys may be affected by the mutation". There's no guarantee the hint is correct. It's also not used by Om to rerun any queries after transactions.

simonb01:11:03

After reading the docs, I thought it was a rule not to transact on things not in the component's query

leontalbot01:11:31

@jannis, ok you put the callbacks them just before the (render..., correct?

jannis01:11:48

You can put them anywhere... inline/anonymous, some function somewhere, an JS object method, your choice.

leontalbot01:11:13

@jannis this is beautiful piece of software simple_smile

leontalbot02:11:16

Are there tradeoffs in using callbacks "intensively" ?

bplatz02:11:07

@leontalbot: I usually only use callbacks when invoking a pure React component (one not wrapped in defui), which I'll pass from the defui component that invoked it. That takes care of 95% of the cases for me, the other 5% I have to pass a query addition to the transact call as it has an impact on components elsewhere in the tree.

bplatz02:11:33

I don't know that I would consider callbacks bad, but I think Om.Next, when using it for what it does well, doesn't require them all that often.

leontalbot02:11:42

@bplatz Any example I could study?

bplatz03:11:01

Nothing stand-alone, but here is a simple example for a click event I just wrote. Just a key in a map I pass with other stuff to a function that returns a standard React component: {:close-fn #(om/transact! this `[(ui/quickview-close ~{:id id})}

bplatz03:11:57

Sorry, can't backtick it because the code has a backtick in it already.

bplatz03:11:16

This is within the (render [this] ...) function of a defui component. So 'this' in this case will refer to the parent.

bplatz03:11:52

And thus this transaction will trigger a render at the parent, if executed.

bplatz03:11:24

Understanding Clojure quote syntax should probably be a pre-requsite for om.next, as it gets used quite a bit in your transactions. Namespacing your transactions is a helpful practice, else the backtick will namespace it for you... you need to prefix it with ~' otherwise.

leontalbot03:11:53

And this :close-fn is defined outside a defui?

leontalbot03:11:55

" I usually only use callbacks when invoking a pure React component (one not wrapped in defui)"

leontalbot03:11:32

I must say, it is still unclear for me what a pure react component is...

thosmos03:11:08

@leontalbot: an example of a pure react component: (def Button (js/React.createFactory js/ReactBootstrap.Button))

jannis16:11:54

Hm. Running a version of my kanban demo built with :optimizations :advanced and no source maps breaks om.next/transact! calls. They throw the "No queries exist for this component" error, despite being executed in the root App component, which has a query: https://gist.github.com/Jannis/cfb533ed03d304e346e6

dnolen17:11:00

@jannis there easily might be something I missed around advanced optimizations

thheller17:11:21

@jannis the externs of react 0.14 are somewhat broken

thheller17:11:20

need to install the react cljsjs package from that branch

thheller17:11:38

and remove om/externs.js

thheller17:11:04

note that I am not running om.next ... so I have no idea if that fixes anything om.next related

thosmos18:11:41

@thheller: your changes make sense. it looks like your changes conform to this line: https://github.com/facebook/react/blob/master/src/isomorphic/ReactIsomorphic.js#L48 which sets React.Component = ReactComponent and is included into the base react.fs file here: https://github.com/facebook/react/blob/master/src/React.js#L24

thheller18:11:26

ReactComponent is only the contructor name, which isn't really relevant in closure

thheller18:11:40

React.Component is what exists in the browser

thheller18:11:47

React.ReactComponent does not

thheller18:11:30

but I don't have a React app in production yet ... so I do not know why I'm the first to notice this error ... seems like someone should have run into this before

dnolen18:11:20

@thheller: pretty sure this is a 0.14 change

dnolen18:11:30

and most people are still on 0.13

thheller18:11:48

yeah if AFAICT no cljs React wrapper (other than om.next) uses the ES6 class as well, it probably doesn't matter than

thheller18:11:57

https://github.com/cljsjs/packages/pull/287 feel free to jump in .. I'm fairly confident that the changes are correct but independent verification would help

martinklepsch18:11:20

Have some people verified that 287 is good?

martinklepsch18:11:22

I’m happy to merge and push it if so

dnolen19:11:30

@martinklepsch: how can you test cljsjs packages is there a page about this somewhere?

dnolen19:11:57

@martinklepsch: cool thanks, in the middle of some other things but I will try this out later

bhauman19:11:58

@dnolen: I'm going to make devcards :watch-atom false work today

jannis21:11:37

@dnolen: The server-side parser returns {app/increment-counter [:app/counter]} for a mutation [(app/increment-counter)] that is (again, on the server-side) implemented with a readf that returns :value [:app/counter] (and an action). Am I understanding it correctly that your todo example simply merges that back into the app state using the callback passed to send?

jannis21:11:31

Ignore the question above. The answer is yes.

jannis22:11:03

@dnolen I may have found a problem or inconsistency. Assume you have a read :app/counter that returns {:value (get @state :app/counter) :remote true}, a mutate 'app/increment-counter that returns {:remote true} and a server-side parser that handles these two, and a component that queries [:app/counter] and triggers [(app/increment-counter)] transactions. When the query is first satisfied, it sends [:app/counter] over to the server, receives the query result from the server (e.g. {:app/counter 5}, merges it and reruns the query, this time returning the value fetched from the server to the component. All good. When you perform the above transaction from within the same component, it sends it over, performs it, receives {app/increment-counter [:app/counter]}, merges that, reruns the query, returns the local value but this time it doesn't also execute the query remotely. So you're stuck with the old value. Is this intentional? It does rerun the query remotely if the transaction is [(app/increment-counter) :app/counter] (in this case the server returns {app/increment-counter [:app/counter] :app/counter 6}) but I thought the keys queried by the component initating the transaction are always included implicitly. Shouldn't that also work with remote queries?

jannis22:11:09

(Sorry for the long read)

dnolen22:11:09

@jannis we consider the component as the thing which needs to re-read but we don’t magically know the keys that are involved as you are suggesting.

jannis22:11:37

What exactly is different when the component re-reads after the transaction (which it does, but not the remote stuff)?

dnolen22:11:10

what we do locally is just a special convenient hack

dnolen22:11:14

we use the component as the key

dnolen22:11:19

but we can’t send a component over the wire

jannis22:11:51

Sure. It does re-execute read :app/counter after the transaction but only gets :value out in that case?

dnolen22:11:07

right but the reason it executes that is because we can get the query from the component

dnolen22:11:23

since we can’t send the component over the wire we can’t use that same trick

jannis22:11:04

I understand we can get the query from the component. But if we can call read for all keys in its query, can we not also execute the remote parts?

dnolen22:11:23

you really don’t want to do this

dnolen22:11:37

it’s just too chatty

dnolen22:11:49

you need fine grained control over server/client interaction after the first load

jannis22:11:49

So ideally, you'd perform an optimistic mutation locally and revert if sending/executing it remotely failed.

dnolen22:11:08

not necesarily

dnolen22:11:40

it’s important to separate collection actions from operations on real logical entities (that have idents)

dnolen22:11:54

update on entities is pretty common

dnolen22:11:12

if you design your interaction around entities you should not have the problem you’ve just described

dnolen22:11:27

this is how the optimistic update example works in the om-next-demo for example

dnolen22:11:41

[(app/increment-counter) [:app/counter 0]]

dnolen22:11:55

then you know you will always get updated properties for that component

dnolen22:11:08

that should be:

dnolen22:11:26

[(app/increment-counter {:id 0}) [:app/counter 0]]

jannis22:11:47

I'll try that

dnolen22:11:09

and this will work regardless of which components have that ident in your UI

jannis22:11:30

And regardless of whether the mutation is executed entirely on the remote end?

dnolen22:11:07

the ident stuff is about synchronization as well as incremental updates

jannis22:11:09

Of course... because the remote end will receive the [:app/counter 0] part as well.

jannis22:11:03

What if the component has an ident but doesn't pass it over to transact!? Will the reconciler implicitly get its ident (instead of its query) after the transaction and refresh based on that?

jannis22:11:13

(I'm guessing yes)

dnolen22:11:32

Yep if it has one

jannis22:11:00

Cool, I've learned a bit more today. Thanks!

jannis22:11:47

I do find it a bit confusing though and I bet questions like this will come up again and again. Like "why no rerender after transact! -> "oh, I have to add :foo/bar to the call" (which I've answered about five times already ;)), "why is the remote state not re-read again after a transaction?" -> "oh, the component has no ident and doesn't add any of the affected keys to `transact!".

jannis22:11:53

But perhaps the amount of confusion can be mitigated by documenting these scenarios very clearly, with examples.

jannis22:11:02

Anyway, I've understood it now, so thanks a lot simple_smile

jannis22:11:43

It makes sense as well. You wouldn't want all queries of a component to be re-executed remotely only because a transaction changed one of them.

jannis22:11:07

But with the convenience trick it's all-or-nothing in that case.

jannis22:11:27

Actually, I like this