This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2015-10-20
Channels
- # admin-announcements (2)
- # beginners (38)
- # boot (12)
- # business (2)
- # cbus (2)
- # cider (8)
- # cljs-dev (32)
- # clojure (154)
- # clojure-brasil (43)
- # clojure-czech (3)
- # clojure-dev (5)
- # clojure-germany (1)
- # clojure-japan (2)
- # clojure-nl (2)
- # clojure-poland (9)
- # clojure-russia (29)
- # clojure-sg (12)
- # clojurescript (147)
- # clojurex (22)
- # community-development (2)
- # cursive (8)
- # events (3)
- # ldnclj (30)
- # ldnproclodo (1)
- # leiningen (1)
- # luminus (34)
- # off-topic (6)
- # om (255)
- # re-frame (1)
- # reagent (22)
- # rum (2)
- # yada (1)
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
ah it seems like it happens when I run transact!
out of a component that does not have queries
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?
@dvcrn: you should should never run transactions from things that don’t implement queries
in future might just disallow it entirely by validating the the transacting component implements a query
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)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
Re: Om Next: I have a simple app with one component, and a reconciler like this
(def reconciler
(om/reconciler {:state app-state}))
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}}
This puzzles me - why is the set-state! invocation coupled with the query feature of Om Next?
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
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
@dvcrn: But in this case, it’s not going to do transactions on the global state, only the component local state.
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
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.
@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?
@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
@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.
@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.
@gabe: haven’t heard of anything but that looks like it would need the CommonJS support work that’s still in flux
@dvcrn: probably a bit too soon for that we still have some simple bugs to work through first
@dnolen: are you referring to https://github.com/clojure/clojurescript/wiki/Google-Summer-of-Code-2015
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
@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
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?
@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.
@monjohn: Yes, I am planning to clone the om wiki, add docs to it, and make links available here for review/commentary
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?
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?
@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
@joshfrench: just remove the key that matches, automatic cache eviction will eventually clean up stuff you are not using
@joshfrench: oh sorry
(it’s not there now, but it will be after we sort out all the small bugs people are reporting)
remove the key from… todo/by-id
?
I beleive that's your database of items...I think you remove it from the todos/list (which is what you are rendering)
and David was indicating that the normalized database-by-ident will eventually be garbage-collected
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) %)))
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'"
@joshfrench: no, you should not touch the tables
but that will likely cause more refetching than is necessary in paging scenarios for example
so should i be operating on something other than state
there?
@joshfrench: no I just said everything that matters
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
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
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
or if you really want to manage it by hand, you will be able to do this - but that won’t be the default
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)
people have already encountered this issue with DataScript and needing to prevent it from growing indefinitely
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
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).
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
It could be that I can leverage :value in the mutate function to compensate...but I have not figured that out yet.
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.
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.
Not that I've fully grokked OmNext yet, but wouldn't a person own a list called "my friends"?
(I'm still struggling with how this works in practice, but that's how it seems it should work...)
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"
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
at the moment the idea of a component calling to the reconciler for transaction does not seem right
"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?
bleh...but who owns the callback? It is derived data that could be peppered about the entire UI
or do I just push it up until I've covered all the possible places it needs to update?
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
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
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)
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).
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
right but talking about this stuff isn’t going to help you understand how Om Next actually works and how to solve this problem
you may need to duplicate some logic to make sure no matter how somebody reads the data the derivation is always applied
if a transaction says :friends/list
needs to be read, everyone listening on that property is going to recompute their data
OK, so the mutate is my problem. It needs to be listing all of the state that is affected by the mutation
right now the problem is that where to specify :friend/list
as a read following a transaction is a bit ad-hoc
this is why I recommended the callback - it should also append the ident of the component that made the callback
the callback is an ad-hoc solution, I want something declarative, but it’s an acceptable stop gap until we have something better
by default transacting components add their ident to the read list following the transaction
that really helps. Thanks David. It will be easier to understand the mutation code now
I'll include this in the docs I'm building...hopefully you won't have to repeat yourself for too much longer
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.
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.
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
and my experience with Om Legacy is I didn’t value simplicity of implementation enough
@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?
ah ... nvm ... I just query for the data always and return []
if the user is not the owner
still rather confused with all these non-static queries ... really looking forward to the union & dynamic query stuff
@thheller: the other thing I’m thinking about is making sure that defui
can load on the backend
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.
Is this because query expression syntax isn't fully available when using a local atom, or am I just doing it wrong?
but nothing stoping true
from being something else, arbitrary user defined interpretation
this would give you a query and you could supply a :send
for :static
. sha the expression, done.
because of built in result merging you can send a dynamic thing as a separate request (if user logged in or whatever)
when calling the parser recursively from read, should we always just pass env unmolested? E.g. is there anything we should remove, like selector?
(I am assuming we've pulled parse from env, and that is what I'm calling to recurse)
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.