Fork me on GitHub
#om
<
2015-10-20
>
thosmos01:10:13

I just created a boot-clj version of @dnolen's om-next-demo. It has server component auto-reloading using @danielsz's System and the usual client auto-reload and BREPL. The Readme describes how to run it: https://github.com/thos37/om-next-demo

dvcrn01:10:58

when exactly is "No queries exist for component path being reported?

dvcrn01:10:23

ah it seems like it happens when I run transact! out of a component that does not have queries

dvcrn01:10:28

that's a good question actually. If only my rootcomponent queries the state and then passes that date down to child components, why do I still need queries on the childs and collect these inside the root?

dnolen01:10:55

@dvcrn: you should should never run transactions from things that don’t implement queries

dnolen01:10:04

use callbacks into components that have queries

dnolen01:10:26

stateless components should be … stateless

dnolen02:10:46

in future might just disallow it entirely by validating the the transacting component implements a query

dvcrn02:10:29

Ah I see. So given a code like

(defui RootComponent
  static om/IQuery
  (query [this]
         '[:app/text :app/html])
  Object
  (render [this]
          (let [{:keys [app/text app/html]} (om/props this)]
            (dom/div #js {:id "wrapper"}
                     (editor {:app/text text})
                     (preview {:app/html html})))))
how would I go on updating state from inside editor or preview? (for example when editor reports that the text changed)

dvcrn02:10:40

should I pass a channel and listen to that channel in my root or something like that?

dnolen02:10:03

I would not involve core.async at all

dnolen02:10:06

just pass a callback

dvcrn02:10:18

let me try that!

dvcrn02:10:24

this seems to work great! Thanks for the hint!

dvcrn06:10:48

if I want to change the appstate from outside a component, would I - use a transaction with the reconciler - (swap!) - async.core channel listener in componentDidMount

grav08:10:36

Re: Om Next: I have a simple app with one component, and a reconciler like this

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

grav08:10:16

If I in componentDidMount do (om/set-state! {:foo 42}), and then update the app-state in the repl, I get an exception from Om Next: Uncaught #error {:message "No queries exist for component path (maps-fun.core/MapThing)", :data {:type :om.next/no-queries}}

grav08:10:01

This puzzles me - why is the set-state! invocation coupled with the query feature of Om Next?

dvcrn08:10:51

the way I understood it: If the component doesn't query for anything (or in other words, is stateless) it doesn't get to do transactions on the state.

dnolen [10:59 AM]
<@U0CJNMS5P>: you should should never run transactions from things that don’t implement queries (edited)

dnolen [10:59 AM]
use callbacks into components that have queries

dnolen [10:59 AM]
stateless components should be … stateless

dnolen [11:00 AM]
in future might just disallow it entirely by validating the the transacting component implements a query

dvcrn08:10:45

I solved it for me by passing a callback into the component back to my root, which queried for the state and passed it into the component in the first place. Alternatively, add queries to your component

dvcrn08:10:02

(I guess you could still do a (swap!) though)

grav08:10:47

@dvcrn: But in this case, it’s not going to do transactions on the global state, only the component local state.

grav08:10:21

ie React.setState. But I see in the source for om.next, that calling set-state! will actually queue up my component for querying, if there’s a reconciler. Seems like a bug to me, since it should be possible to have a component that does not use queries, but does use component local state

grav09:10:04

Ok, so I’ve gone the query way, and now I have a new problem - when updating the app state via queries, the ‘next’ param of componentWillReceiveProps is nil.

jannis10:10:39

@grav: It's supposed to be (om/set-state! this {:foo 42}) because you're trying to set the local state of the component, isn't it?

jannis10:10:57

@grav: I noticed that set-state! seems broken at the moment, it doesn't rerender the component in question. Not 100% sure whether my assessment is correct but see my comments here: https://github.com/omcljs/om/pull/427

grav10:10:13

@jannis: you’re right, i’m missing a this in the code above (but just in the example).

grav10:10:52

@jannis: I actually don’t need a re-render in this case, but you’re right that it should re-render as it does in react.

jannis11:10:16

@grav: I agree by the way, local state should be ok without queries and there shouldn't be a warning. I'm not sure I got one when I tried it.

dnolen11:10:40

Yeah that's just a bug

dvcrn12:10:27

Anyone played around with om.next and react native yet?

gabe14:10:40

Has anyone tried to get Radium working w/ Om?

dnolen14:10:24

@gabe: haven’t heard of anything but that looks like it would need the CommonJS support work that’s still in flux

dnolen14:10:51

@dvcrn: probably a bit too soon for that simple_smile we still have some simple bugs to work through first

gabe14:10:13

that would be a huge win

dnolen14:10:29

integrating wider things from the React ecosystem is still a big challenge

dnolen14:10:00

in the meantime the only thing to do is to make custom builds of React that includes the other stuff that you want and to provide externs for those bits that you actually use

dnolen14:10:20

it’s a little bit more work but that’s about it

dnolen14:10:09

but also what you have to do in JS anyway

gabe14:10:16

thanks for the pointers simple_smile

dnolen14:10:20

minus the externs bits of course

nowprovision14:10:39

@dnolen, been following bits and pieces on twitter only about omnext, so apologises if missed some context, at what point do you think there will be a server-side router in style similar to falcor and possibly an initial full stack implementation (suspect datomic), or might suffice when do you see the Remote Synchronization Tutorial being ready

dnolen14:10:44

Already happened

dnolen14:10:03

See om-next-demo on github

tony.kay15:10:18

OK, so components that have no queries are not supposed to run transactions on the state. What if I have a button toolbar that is meant to do actions (e.g. "Launch Rocket")? It is a stateless component, as it needs no data, but it clearly needs to run an action. Should I treat the "action" as something other than a transact!, and have any side effects (hit target) end up in my application state later? E.g. From @dvcrn summary of David's comments earlier today transact! probably doesn't come into play, since that is targeted at direct app state for UI rendering, right?

dnolen15:10:10

this is already an established React pattern that works

tony.kay15:10:49

I'm very happy. I think the core bits all finally clicked.

dnolen15:10:46

I may actually make this work, but the pattern will hold

dnolen15:10:54

pure reusable things should not invoke transactions

monjohn15:10:53

@tony.kay: If you write up what you have learned about om.next for your team, it would be great if you could post it. I am still waiting for it all to click.

tony.kay15:10:23

@monjohn: Yes, I am planning to clone the om wiki, add docs to it, and make links available here for review/commentary

tony.kay15:10:36

I'll push that to my own fork on github

tony.kay15:10:56

then if anything is good enough for inclusion, David can use it

joshfrench17:10:30

if i have a local-only parser with this state: {todos/list [[todo/by-id 1]], todo/by-id { 1 { :name “foo” }}} what’s the right way to add/remove items? am i responsible for updating both top-level keys or do i update one and then re-normalize somehow?

tony.kay17:10:51

I am wanting just a bit more clarification on stateful/stateless components. If there is an interstitial component in a rendering branch of my tree A->B->C that has no query of its own (e.g. B needs no state itself, but renders a stateful thing C), should I place a query on B like [{:c (om/get-query C)}] so that the generated UI tree has the same number of "stateful-looking" segments?

tony.kay17:10:34

@joshfrench: So, I think you should either start with normalized state (todo/by-id indicates you are making an index, but you've embedded that in your items), or use denormalized state and have om-next normalize it for you. You've kinda got the two mixed together

tony.kay17:10:14

E.g. {:todos/list [[:todos/by-id 1] ...] :todos/by-id { 1 {:id 1 ...} }}

tony.kay17:10:42

then your mutation would change the list

dnolen17:10:47

@joshfrench: just remove the key that matches, automatic cache eviction will eventually clean up stuff you are not using

dnolen17:10:20

(it’s not there now, but it will be after we sort out all the small bugs people are reporting)

tony.kay17:10:20

I misread your data structure...thanks for catching that David

dnolen17:10:27

also after we get unions into queries

joshfrench17:10:09

remove the key from… todo/by-id?

tony.kay17:10:55

I beleive that's your database of items...I think you remove it from the todos/list (which is what you are rendering)

tony.kay17:10:24

and David was indicating that the normalized database-by-ident will eventually be garbage-collected

tony.kay17:10:10

your app state refs [:todos/by-id #] are your "references/pointers" to the data.

joshfrench17:10:08

so this is what i have in my mutator at the moment:

(let [deleted [:todo/by-id id]]
      (swap! state update :todos/list #(vec (remove (partial = deleted) %)))

tony.kay17:10:09

So, implementing garbage collection is "walk from a root set, put refs in a 'mark set' as you see them, then at the end remove anything from the normalized db that isn't 'marked'"

dnolen17:10:19

@joshfrench: no, you should not touch the tables

dnolen17:10:22

only stuff you actually created

dnolen17:10:36

the tables will need to be controlled by Om Next

dnolen17:10:54

well actually to be honest

dnolen17:10:07

you can remove something if you know you’re not going to use it

dnolen17:10:30

but that will likely cause more refetching than is necessary in paging scenarios for example

dnolen17:10:40

if you’re actually deleting something for the user than that’s different

dnolen17:10:20

but really to be safe you should probably leave it alone

joshfrench17:10:23

so should i be operating on something other than state there?

dnolen17:10:27

esp. failed deletions

dnolen17:10:36

@joshfrench: no I just said everything that matters

dnolen17:10:17

I would not build production stuff with Om Next yet so I wouldn’t be concerned about the fact that we haven’t implemented cache eviction

dnolen17:10:28

you should only be deleting from your own stuff not the generated tables

joshfrench17:10:07

so in the absence of a remote that would manage the data for me, there’s no “right” answer other than maybe removing said item from the List component's props

dnolen17:10:47

I didn’t say anything about remote

dnolen17:10:56

everything I said above applies

dnolen17:10:05

the whole point is that remote never matters

tony.kay17:10:47

This confuses me as well. In your own example on identity and normalization your "init state" is not an atom...so there is no way to change it. Your mutation functions operate on the state in the tables

tony.kay17:10:25

So "don't touch the tables" seems to contradict the example

dnolen17:10:44

updating them is not the same as removing them

dnolen17:10:04

state functions as cache

dnolen17:10:16

if you start loading pages of data you will not want to sort out when to evict them

dnolen17:10:28

updating them is another matter

dnolen17:10:00

even if everything is local you may generate data that you throw away

dnolen17:10:15

simpler to just let Om Next manage the throwing away part for you

dnolen18:10:11

there will definitely be knobs for this

dnolen18:10:19

and if you use DataScript you will be on your own

dnolen18:10:31

or if you really want to manage it by hand, you will be able to do this - but that won’t be the default

dnolen18:10:40

similar to the other knobs on the reconciler

dnolen18:10:43

to be clear the cache eviction thing is just a general problem if things are generated and stored in terms of explicit graphs (not handled by host GC)

dnolen18:10:07

people have already encountered this issue with DataScript and needing to prevent it from growing indefinitely

tony.kay18:10:54

Right, totally clear there. So, I may have a misunderstanding about normalization. Is there something inherent in the design that requires the app state be normalized in a specific way? (e.g. THE specific way shown in the tutorial). Obviously the comment about datascript indicates "no", but it seems like there's some extra benefit to having the app state follow a specific form even though the parser is supposed to be the UI's interface to the data

dnolen18:10:55

the state doesn’t need to be laid out in a specific way

dnolen18:10:10

but the [key id] thing is just dead simple and lets us use get-in

dnolen18:10:28

and I recommend people just stick to this convention

tony.kay18:10:59

It seems the answer to my A->B->C question is "no", since the example in the tutorials shows such a case (interstitial ListView without a query).

dnolen18:10:03

putting a component inbetween is not an issue

dnolen18:10:09

we only look at the path of components that have queries

tony.kay19:10:28

Is it considered "ok form" to call (transact! reconciler ...) on a stateful component that has queried for an attribute that the underlying parser has derived from state that might be rendered elsewhere? I could pass a callback from the root (but this could be a lot of boilerplate through a chain of render). Running it on "this" won't work, because the derived data can cause other bits of the UI (that have use the derived data) to need to update. The "local reasoning" model tells me I should not care about how it is stored in the state, but the fact that it is derived data means I sorta have to care...because the UI won't update correctly otherwise. Yet, I have an abstract mutation function that "makes total sense" with respect to the derived data. I have a simple example with comments in question form at: https://github.com/awkay/tutorial/blob/master/src/om_tutorial/core.cljs#L73

tony.kay19:10:32

It could be that I can leverage :value in the mutate function to compensate...but I have not figured that out yet.

tony.kay19:10:47

I guess a reasonable option would be to make top-level functions that call transact on the reconciler, and call those from the UI component. That preserves local reasoning, and since it really is kind of a "remote control" thing perhaps it is the "right" thing to do.

dnolen19:10:30

I’m not sure I’m following this line of reasoning

dnolen19:10:38

do you have a concrete UI thing here you are trying to model?

tony.kay20:10:25

yes, linked at that line

tony.kay20:10:09

is-friend is a derived attribute of person (does the person exist in a set of IDs that are my friends). Un-friending and friending are actions I'd like to allow when I display a person.

tony.kay20:10:28

the parents don't technically own that state

tony.kay20:10:39

so a callback seems tedious and unnecessary

drcode20:10:42

Not that I've fully grokked OmNext yet, but wouldn't a person own a list called "my friends"?

drcode20:10:35

You'd pull that in with the query syntax, then could modify as desired?

drcode20:10:15

(I'm still struggling with how this works in practice, but that's how it seems it should work...)

drcode20:10:08

Oh, I see, you want access to this from the "friend" side

tony.kay20:10:27

My point isn't really what you "could" do. The example I've built has a clear structure, local reasonsing, and relies on the parser to isolate the UI tree from the state. My "abstract operations" should just "make sense"

tony.kay20:10:42

make-friend, and un-friend

tony.kay20:10:16

I need to know if you are a friend to render the control correctly, but Person doesn't own that state in any way in the real app state

drcode20:10:17

Yeah, I'm talking out of my element- Need to study the problem more

dnolen20:10:10

@tony.kay: I still don’t understand what you are saying

tony.kay20:10:28

hm. Did you look at the code at line 73?

dnolen20:10:50

at the moment the idea of a component calling to the reconciler for transaction does not seem right

dnolen20:10:55

that should only happen outside

tony.kay20:10:55

"at the moment"...is my example a case where it might be ok (from a "framework should support"), but just might break due to alpha?

dnolen20:10:14

@tony.kay: yeah that’s a perfect example where you should be using a callback

dnolen20:10:31

just because it isn’t convenient isn’t really relevant

tony.kay20:10:36

bleh...but who owns the callback? It is derived data that could be peppered about the entire UI

tony.kay20:10:57

or do I just push it up until I've covered all the possible places it needs to update?

dnolen20:10:02

some other abstraction may appear here, but I have no idea what it will be and not going to discuss it until I do have an idea

tony.kay20:10:16

fair enough

dnolen20:10:19

parents often represent collections

tony.kay20:10:28

as do siblings

dnolen20:10:31

parents control collections and should pass callbacks

dnolen20:10:25

PersonList is the place for this stuff to go

tony.kay20:10:30

just keep in mind that top-level app state may lead to derived values, where there is an unclear path to all of the places it is rendered

dnolen20:10:14

that’s a completely separate issue from who triggers the fundamental mutation

tony.kay20:10:38

and I don't think it will re-render correctly until I push the callback up one more level...which gets noisy (since there is a sibling list that needs to re-render)

dnolen20:10:07

keep the re-rendering issue completely out of this discussion

dnolen20:10:14

that’s just a completely different problem

dnolen20:10:59

if we talk about one thing at a time - less chance for muddying the waters

tony.kay20:10:10

I didn't realize they were completely separate

dnolen20:10:11

put the transactions into parents that represent the collection

dnolen20:10:32

that answers only that problem

dnolen20:10:42

keeping things in sync has nothing to do with this advice

tony.kay20:10:56

but the collection isn't changing...a derived fact in a person is..and I might have a completely different widget in the upper corner that renders the size of the friend set (you have 10 friends).

tony.kay20:10:06

The location of the transaction should be independent of the rendering tree

dnolen20:10:20

you’re talking in terms of metaphors that have nothing to with Om Next

dnolen20:10:29

it’s irrelevant that you have some derived thing about one person

tony.kay20:10:29

I'm trying to give an example

tony.kay20:10:15

The list of friends is a derived UI artifact. The friend count is a derived number. The controls on people rendered throughout the UI are using this set to derive their LAF

tony.kay20:10:33

Why should the family or friends "list" own the transaction?

dnolen20:10:48

right but talking about this stuff isn’t going to help you understand how Om Next actually works and how to solve this problem

dnolen20:10:24

1) every person is should have an ident

dnolen20:10:33

keeping people in sync regardless of “derived” or not is handled by this

dnolen20:10:55

you may need to duplicate some logic to make sure no matter how somebody reads the data the derivation is always applied

tony.kay20:10:30

I get Ident. I really do. I also see how the sync of person itself works.

tony.kay20:10:42

the control on Person will change fine due to ident

tony.kay20:10:47

no matter where the xaction runs

tony.kay20:10:51

got it solid

dnolen20:10:58

ok if you understand that then here’s the other thing

dnolen20:10:04

the indexer tracks all kinds of keys

dnolen20:10:08

ident is one class of key

dnolen20:10:16

:friends/list is another

dnolen20:10:50

if a transaction says :friends/list needs to be read, everyone listening on that property is going to recompute their data

tony.kay20:10:10

AH. that clarifies value...I was missing that bit

tony.kay20:10:42

OK, so the mutate is my problem. It needs to be listing all of the state that is affected by the mutation

dnolen20:10:45

right now the problem is that where to specify :friend/list as a read following a transaction is a bit ad-hoc

dnolen20:10:04

this is why I recommended the callback - it should also append the ident of the component that made the callback

tony.kay20:10:51

sure...you need all of the places that one thing is rendered to be updated by id

dnolen20:10:53

the callback is an ad-hoc solution, I want something declarative, but it’s an acceptable stop gap until we have something better

dnolen20:10:36

but the Om Next model is one oriented around observed keys

dnolen20:10:56

by default transacting components add their ident to the read list following the transaction

dnolen20:10:07

this covers all the basic cases, but not the action at a distance or sibling one

dnolen20:10:38

so you can add the ones you know you need

dnolen20:10:25

this is NOT documented anywhere so I’m not saying this is at all obvious

tony.kay20:10:27

that really helps. Thanks David. It will be easier to understand the mutation code now

tony.kay20:10:03

I'll include this in the docs I'm building...hopefully you won't have to repeat yourself for too much longer simple_smile

dnolen20:10:57

once all the basic bugs & union get sorted I’ll probably spend some time on this.

dnolen20:10:48

but it may turn out that the callback approach just mean Om’s implementation remains very simple which may trump declarative affordances in this case.

tony.kay20:10:47

yeah....it does sound dreamy to be able to say "I depend on the 'who's a friend' fact", index all the components, and then be able to do a mutate that says 'who's a friend' changed...without it being tied to a rendered collection.

dnolen20:10:51

I’ve always put a high value on Om’s source code being something you could read in a day, and the new code base is incredibly simple to debug

dnolen20:10:08

and my experience with Om Legacy is I didn’t value simplicity of implementation enough

tony.kay20:10:12

seems like you're most of the way there

dnolen20:10:18

when I get a bug report it should be immediately obvious what is wrong

tony.kay20:10:50

I agree with your philosophy. Simple is better.

dnolen20:10:59

so far it’s been working

dnolen20:10:11

nearly all the bug reports are fixing 5-10 lines of code

tony.kay20:10:24

Yeah, I've noticed....oh crap..late for a meeting!

tony.kay20:10:33

thanks again!

thheller20:10:17

@dnolen got another mind bender ... in my CMS I have the notion of an "owner" for an object. Say I want to display object #1, if and only if the current user is the owner the object I want to display more data (from the server). I only know who the owner is after loading it. I assume I need to sort this out in the read, probably on the server?

dnolen20:10:28

yeah stuff like that I would fix in the server side parser

dnolen20:10:41

to avoid round-tripping

thheller20:10:59

still can't figure out the query though

thheller20:10:14

ah ... nvm ... I just query for the data always and return [] if the user is not the owner

thheller20:10:00

still rather confused with all these non-static queries ... really looking forward to the union & dynamic query stuff simple_smile

dnolen20:10:37

@thheller: the other thing I’m thinking about is making sure that defui can load on the backend

thheller20:10:13

eek too much cljc for my taste

dnolen20:10:14

yeah not sure yet, but the defui bits hold the query

dnolen20:10:26

the render stuff doesn’t really matter

dnolen20:10:57

def not a priority if more dynamic things can be solved by other meanas

thheller20:10:06

yeah but the file containing the defui probably has a bunch of other cljs in it

dnolen20:10:11

but again “dynamic” here is really a bad word

drcode20:10:23

So... in the "Identity and Normalization" tutorial... suppose I wanted to query all the names of people in list one for a container... I'd I would think I'd write something like (parser {:state st} '[{:list/one [:name]}]) but that doesn't seem to work.

dnolen20:10:43

the tutorial is just going to be about unions in queries

drcode20:10:07

Is this because query expression syntax isn't fully available when using a local atom, or am I just doing it wrong?

dnolen20:10:11

@drcode: that doesn’t work because Om Next doesn’t do anything

dnolen20:10:19

there isn’t any magic at all in Om Next simple_smile

dnolen20:10:23

you wrote the parser

dnolen20:10:30

if you get more data than what you expected ...

dnolen20:10:33

well that’s the parser you wrote!

drcode20:10:48

I see, the "query expression syntax" is a recommendation, not a built-in feature

dnolen20:10:03

it can’t be a built in feature

dnolen20:10:07

if it was it wouldn’t be pluggable

dnolen20:10:23

this isn’t different from routing as I’ve said elsewhere

drcode20:10:53

thanks, that makes sense.

dnolen20:10:08

@drcode: but to answer your question directly

dnolen20:10:19

parse gives you :selector in the env param

dnolen20:10:28

you can use this to do (select-keys …)

dnolen20:10:35

in Datomic/DataScript you can use it for pull

drcode20:10:51

yeah, I noticed :selector, so all the hooks are there to write the full support

dnolen20:10:39

another reason I called it parse, it’s up to you to interpret the expressions

dnolen20:10:12

oh so the other cool idea i had today

dnolen20:10:21

HTTP caching can just work

dnolen20:10:33

right now reads can return :remote true

dnolen20:10:54

but nothing stoping true from being something else, arbitrary user defined interpretation

dnolen20:10:03

for example :static

dnolen20:10:38

then (parse {…} […] {:filter :static})

dnolen20:10:02

this would give you a query and you could supply a :send for :static. sha the expression, done.

dnolen20:10:31

because of built in result merging you can send a dynamic thing as a separate request (if user logged in or whatever)

dnolen20:10:05

so the same thing that gives us merged views for local/remote

dnolen20:10:10

can give us merged views for static/dynamic

dnolen20:10:40

yet another one-up on Relay & Falcor 😄

tony.kay22:10:33

when calling the parser recursively from read, should we always just pass env unmolested? E.g. is there anything we should remove, like selector?

tony.kay22:10:03

i.e. if we were calling parser to recurse into the selector

tony.kay22:10:24

(I am assuming we've pulled parse from env, and that is what I'm calling to recurse)

dnolen22:10:42

you should do whatever seems appropriate in parse for the recursive call

tony.kay22:10:49

fair enough

locks23:10:14

this channel is on 🔥

tony.kay23:10:56

To anyone interested. I'm working on an Om Next Overview guide on a fork of the wiki at: https://github.com/awkay/om-wiki-fork/blob/master/Om-Next-Overview.md It has a big disclaimer that I may not actually have a full clue, but if anyone is wanting to review it and give feedback, I'd appreciate it. I'm currently writing up how you write your read function and use the parser.