This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2015-11-13
Channels
- # admin-announcements (6)
- # beginners (19)
- # boot (1)
- # cbus (2)
- # cider (3)
- # clara (24)
- # cljs-dev (4)
- # cljsrn (18)
- # clojure (168)
- # clojure-boston (1)
- # clojure-dev (55)
- # clojure-russia (199)
- # clojure-sg (2)
- # clojurescript (38)
- # clojurex (1)
- # core-async (15)
- # css (16)
- # cursive (62)
- # datomic (23)
- # editors-rus (17)
- # events (3)
- # funcool (1)
- # hoplon (360)
- # ldnclj (37)
- # lein-figwheel (11)
- # leiningen (1)
- # nginx (1)
- # off-topic (13)
- # om (361)
- # onyx (1)
- # re-frame (56)
- # reagent (24)
- # robots (1)
- # spacemacs (46)
- # yada (9)
Hrm. There must be something very wrong here. This works fine:
app.reconciler=> (cognitect.transit/read (om.transit/reader) "[\"~#om/id\",\"~u9fff7aad-e336-4d21-a582-86cc133b0ddd\"]")
<#C06DT2YSY>/id["9fff7aad-e336-4d21-a582-86cc133b0ddd"]
But this doesn't:
app.reconciler=> (ajax.core/POST "" {:params (om.next/tempid) :reader (om.transit/reader) :writer (om.transit/writer) :handler #(println % (type %))})
#object[Object [object Object]]
The server returns application/transit+json
and the body of the response is ["~#om/id","~ua4477301-123c-4fe0-b4dd-233749caaa2d"]
. I suspect cljs-ajax
is messing with the response body in a way that confuses transit/read
...Nevermind. Passing :response-format :transit
to POST
did the trick... so this took me over an hour.
Don't forget to sleep @jannis ;-)
@leontalbot: Aww, thanks You're right
When I use om/computed
componentDidUpdate
is called twice. First with om/props
or prev-props
having the correct props and then again with {:om.next/computed {...}}
. Why is that?
man, after figuring out that normalization stuff everything just started working as I hoped/expecting things would be
question: I have a post component which looks like this:
(defui Post
static om/Ident
(ident [this {:keys [id]}]
[:post/by-id id])
static om/IQuery
(query [this]
'[:id {:user [:username]} :content])
if I understand this query fragment correct, it says give me :id
:content
and :user
(but only :username
) from that. Post is identified by id.
My server is returning data like this (after parsing):
{:app/posts [{:user {:username dvcrn}, :content Hello World!, :id 1}]}
(The parent component queries like this:
(defui TimelineComponent
static om/IQuery
(query [this]
(let [subquery (om/get-query Post)]
`[{:app/posts ~subquery}]))
)my thinking was that the :user
key will be handled like every other key in that query during normalisation. so :post/by-id 1
should be {:id 1, :user {:username dvcrn}, :content Hello World!}
ah, am I supposed to return the data flat maybe? like:
{:app/posts [{:user/username dvcrn, :post/content Hello World!, :post/id 1}]}
hrm you are right. I tested it with the normalisation tutorial (but with "name" being embedded into "user") and the problem seems to not be present
In my case, the first component encountered in the tree that implements ident is correctly normalized, but the data nested inside that map is not normalized i.e. the normalization does not recurse into the identified thing and replace maps with refs. It doesn’t create tables for the components getting the nested data.
What version of cljs are you using? I’ve had similar issues in the past that were fixed by updating to the latest version (1.7.170 at this time).
In my datascript/om.next app i would like to specify that a component should not be rendered if only a :db/id is received. Here is a scenario in 30 loc: https://gist.github.com/ThomasDeutsch/8a88f6d63063e892daee Would this make sense?
@thomasdeutsch: Sure, why not? If the subcomponent queries demand props that you can't satisfy because they don't exist / aren't set, you shouldn't pass the incomplete data down to those subcomponents anyway - you'd be violating the contract established by their queries.
Although I'd probably make sure that what ends up in the database is stuff that is actually useful (e.g. through validation / schemas / etc.) and can be rendered.
@noonian: I would test with master (made some other fixes) and change this and see if it still fails for you.
if does please provide the components along with the data as well so I can easily make a test case. Thanks.
UUID hashing in transit-cljs was a nasty bug. Took me an hour or two last night to figure out why in default-migrate
, (get-in pure old)
returned nil
, resulting in all local entity props to be erased when migrating from a tempid
to a real one.
I didn't notice it at first yesterday because along with the mutation, I also re-read the corresponding data. So it would migrate to something empty, then be overwritten by the new data right away. Dropped the read and ran into the bug right away.
If cljs-ajax
had bumped its dependency on transit-cljs
, it wouldn't have been an issue. This should solve UUIDs in any client that uses cljs-ajax
: https://github.com/JulianBirch/cljs-ajax/pull/113
You're probably right. cljs-ajax doesn't even make it possible with less code. The extra dependency/complexity may not be worth it.
I might have completely misunderstood what it is used for, but doesn't lein override the package's dependency if you use :scope "provided"
?
or is it for something totally different and I'm just saying nonsense?
@anmonteiro: people keep copying that part blindly
thanks for clearing it up for me
it just means this is a dependency but don’t actually add it to the artifacts lists of deps
even then, only in libraries that other people will use, right?
doesn't really make sense on our own projects, then
if I'm understanding correctly
by projects I mean apps
cool, thx
but if your library actually needs a specific version of either of those things you need to pin.
yep, that makes sense
haven't been following the latest updates to Om, been caught up in schoolwork; what does transform-reads do ?
@jannis wasn’t much to do since there’s all these helpers now that are needed elsewhere
@anmonteiro: it fixes up reads so that remotes get the right thing
selector-wise?
query* now
I see
so it gets those queries from the components where they are declared
and passes them to our read fn?
@dnolen: Excellent. focused-query
is what I tried to "emulate" in my hack (except only for the initiating component itself). full-query
is something the hack didn't even consider. Nice. Gonna try it now
Everywhere Mostly to pass the ident of a component to callbacks. But I can live with
ident
as well, it's only marginally longer.
@dnolen: Somehow performing a local mutation without specifying any reads causes the entire query of the initiating component to be sent over the wire again.
@jannis if you look at transform-reads
you should see it only applies if the transaction fragment is a keyword
Same here. tx
only contains the mutation by the time it arrives at transact*
. I'll debug a bit more.
Okay, it happens only when the app is reloaded after changing its code. If I refresh the page in the browser it works. I had this weird behavior before...
That's the initial read but that's another thing. I've ran into situations where the app has behaved differently after refreshing and after reloading (regardless of the state). One time it was caused by doing (for [item list-of-items] (item item))
by accident (data and component had the same name...). I must be doing something like that again.
@jannis so I’m interested in the case the where you actually supply a key, does that work for you?
With my app behaving in two different ways I can't say which one is actually the right one.
It could be that it transforms [(app/something) :foo]
into [(app/something) [({:foo ...} {:param :bar})]]
instead of [(app/something) ({:foo ...} {:param :bar})]
but I can't be sure.
I think the app is behaving normal again. @dnolen It's not transform-reads
that is the problem. It only sends the entire component query over the wire if mutate returns an :action
. So somewhere else it must accidentally go remote when updating the component after the action.
@jannis easy way to test - parse w/ this mutation query provide :remote
as 3rd argument
Will do. (om/transact! reconciler '[(app/toggle-expanded ...)])
triggers the send as well, so it's not the component being added implicitly that's the problem.
Is there a way to see cljs data structures and not the generated JS ones when debugging in chrome?
there’s a Chrome extension now too https://github.com/binaryage/cljs-devtools
@dnolen: Right. @tony.kay pointed me to it. It may be simple. The "problem" is that something triggers schedule-render!
. Could be the state watch. That calls reconcile!
, that fetches the root query and gathers and schedules the read sends I'm seeing.
@dnolen: is the second one to pick up possible new stuff that needs to happen due to the state change?
perhaps I'm misunderstanding something: the parser is run with a nil target and each remote on every re-render, correct?
my reading of the code says the former...gather sends is running the parser on all remotes
following the om.next tutorial (the datascript integration part), I get :
pull_parser.cljc:223 Uncaught #error {:message "Cannot parse pull pattern, expected: [attr-spec+]", :data {:error :parser/pull, :fragment nil}}
. Is there some change to do from the README ?I had assumed your example in todomvc of conditional remote logic and comments on conditionalized read meant you intended that
Does that mean :remote true
doesn't have to be conditionalized at all because it will only be executed during initial read and transactions that ask for the key to be re-read?
So, you need the conditions either way...but I can see why you only need to do the remote rescan in special cases
@dnolen: but what if the incoming data from the remote read changes the app state in a way that wants more remote reads?
transact!
doesn't gather keys from the root though, so you can choose whether or not to re-read remotely by including it in the transaction. If you don't - local re-read only. If you do - local and remote re-read.
I find conditional :remote
confusing. The only situation where the client knows to re-read remotely is on initial render and whenever a mutation happens. That information is not available inside read :foo
.
straining my brain to think of an example...had not thought about it as a two-way binding
Fair enough. You could argue that polling/pushing server data into the client can happen separately to keep the app state up to date.
Although in that case... why have remotes at all and not just wait for something else to make data available in the app state?
Also: if :remote
is conditional, gathering sends for (om/transact! '[(foo/bar) :baz])
would never return any.
I think you want to be able to state: after this mutation, :foo
or :bar
may be out of date and I want to re-read it. But if there already is a :foo
or :bar
in the app state, that wouldn't end up as a query at the remote end.
I haven't looked at transact! implementation enough, so not sure what else it might do
@jannis: This is what I am doing... so the query ends up being more of a subscription request than an request for data. The server will send updates, and the client assumes it always has current data.
either way, the thing that remains: we need to understand better how it is supposed to work
Datascript allows the subscription to be more broad, to reduce the server-side need to re-running queries. So it can push attribute changes that may not actually be required, but then the client will re-execute the query once the changes are received. Om helps here in that it will limit re-rendering if nothing has changed.
@tony.kay: You could clear the invalid stuff. But think about the tempid feature, for instance. If you'd say saving a temporary entity will remove it from the local state parallel to sending to the server for creating it there, the whole tempid migration story makes no sense.
so, the parser (your parser) must do the "right thing" to get the follow-on reads to ever happen
And if you didn't clear the temporary entity but marked it as invalid instead, you'd have to clear the invalidation hints when merging the server data or during migrate.
tempid strategy is just a special case that’s outside of any specific application (like GC)
I guess instead of deleting an entity with a tempid before transacting its real creation, read
could return :remote true
if the entity has a temporary id.
@dnolen How would I import styles from a css file per component? http://ai.github.io/postcss-way/?full#29
@stephenway: Om Next doesn’t care about that, same as it doesn’t really care about DOM in the big picture
@stephenway: CSS is very specific to web, Om Next is designed to work with Web, iOS, Android
tempid is just about building up the value you want before committing, regardless of when you get to commit it
Let's ignore the tempid stuff for the moment. What I don't understand is how passing a read to remotely executed transact!
would be useful if the corresponding read function had a conditional :remote
that is only returned if there is no data in the local app state.
How would you express "please have the post-tx read include the remote part after this transaction"?
you’re talking about the case where it’s already in the app state but you need a fresh thing
How does the quoted thing know what the remote AST(s) will be if they aren't returned by read
?
@jannis this stuff wasn’t completely set in stone though since I wasn't really sure about runtime remote computation
Ok. I haven't modified AST remotes yet, so others would be better judges on how this is done best.
transform-reads
seems to turn [(app/dosomething) :foo/bar :baz]
into [(app/dosomething) [:foo/bar] [{:baz [:db/id :baz/name]}]]
where it should be [(app/dosomething) :foo/bar {:baz [:db/id :baz/name]}]
@dnolen: I am confused enough now... wasn't gather-sends
fine after all if :remote
is conditional?
Doesn't that mean you could always return :remote true
and it would still only be executed "1st time and only after transactions or query changes"?
@dnolen: This is perfectly desirable, it's what I've been arguing for all along (unless I misunderstand yet again). 😉
@dnolen: transform-reads
works fine now, the gather-sends
on rerender is also gone and it's all working smoothly now. Good stuff!
@jannis: but the parser will run from root, so anything that says remote true
will be included in the remote read, I believe
@dnolen: Ah, the quote thing is exactly what I needed for what I'm working on...I was doing something a lot more convoluted
I have a component that queries for a nested map, and then creates a set of child components. I am passing transact
callback to each of those components I create. This should be enough to trigger a re-render correct? I shouldnt need to implement Ident
and IQuery
on the child components right?
I think I've seen some conflicting information about returning a vector from a join. Is that best practice, or is a map perfectly acceptable. i.e. [{:me [:name]}]
returns {:me [{:name "brian"}]}
, or is returning {:me {:name "brian"}}
perfectly acceptable?
Both work, I just thought I saw somewhere "always return a vector from a join". In certain cases I'd prefer to return a map from a join, I just want to make sure I'm not messing up anything internally if I do so.
@bplatz: I might have made such a mistake in my (unstanctioned) doc...I didn't realize singleton case was ok until fairly recently
Do you have any plans to make the relationship between the shape of an ident and how things are normalized more configurable?
OH: "i swear he’s watching our private slack channel, the # of times he’s just happened to drop whatever feature we’ve been wanting is uncanny"
@dnolen I assume doesnt force sending reads to remotes if the remotes are conditional in the corresponding read functions and thus may not be returned from via e.g. :remote, :static or whatever? Or can it do that somehow?
But the resulting queries don't include modifications to the AST that read might make if the condition for a remote is true?
Yes. We may be talking about two different aspects. When you read :foo/bar the usual way, its read function would be called and that might return :value plus perhaps :remote. The latter could be true or it could be a modified query/AST, right? Like in your static vs. dynamic example. Om would then use that modified query and send it to the remote. Actually, with force I assume read is still called and in there you can then detect that (via target?), bypass any conditional stuff and to get the same modified query even if the condition you'd normally use may be false?
whether quoting will allow you do something conditional instead of going some other way
My confusion is mostly about what force/quoting actually does. Like: what would the resulting query look like compared to not forcing? How is it different from an unconditional remote in the corresponding read function? If read has (if <cond> {:remote ...modify AST...})
, will the forced query have that modification if the cond is false. As for the latter, I'm guessing: only if the condition is aware of checking :target. Or if force is passed that same modified AST already.
the first time you read you know you want to go remote because you cannot find it in the app state
unless someone forced a read and you know that happened because the AST now has :target
Ok, cool! :) So you can make it (if (or <cond> <has target?>) {:remote ...})
basically.
I'll try tomorrow. Put the laptop away because I needed a break (if only from hacking myself) 😉
I was expecting more magic but looked at force and thought: "but how das that force anything?" So it doesn't per se but it makes forcing possible. Good, I can take a nap at ease now. 😉
Or does it also send the read over even if the local read function doesn't do anything / doesn't return anything for the target remote?
@jannis I just changed the behavior, previously it would go regardless, which was the wrong thing (that’s what I stated earlier)