This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-02-26
Channels
- # aatree (1)
- # admin-announcements (1)
- # beginners (84)
- # boot (239)
- # braid-chat (5)
- # braveandtrue (20)
- # cider (42)
- # cljsjs (4)
- # cljsrn (31)
- # clojars (18)
- # clojure (101)
- # clojure-austin (1)
- # clojure-gamedev (4)
- # clojure-madison (2)
- # clojure-poland (30)
- # clojure-russia (37)
- # clojurescript (95)
- # core-async (7)
- # cryogen (1)
- # css (3)
- # cursive (14)
- # datomic (8)
- # devcards (7)
- # dirac (2)
- # editors (2)
- # emacs (2)
- # funcool (1)
- # hoplon (15)
- # immutant (30)
- # ldnclj (37)
- # lein-figwheel (6)
- # leiningen (8)
- # luminus (5)
- # mount (1)
- # off-topic (59)
- # om (325)
- # om-next (7)
- # onyx (95)
- # parinfer (162)
- # proton (1)
- # re-frame (4)
- # reagent (23)
- # slack-help (4)
- # yada (43)
@anmonteiro: I think that’s it, later we can be careful to only dump tables we know we created
@tmorten what does the “result” from the mutation look like that gets passed to default-merge
?
when I do a remote mutation and return a result from the server, the tempid migration is working fine but my server response is getting merged into my app-state if I use the default merge. Like my app state has a migration name as a key`place/create` and the response as a value:
{:keys [:places/list],
:tempids
{[:place/by-id <#C06DT2YSY>/id["b6d04510-384d-4912-a4eb-64f6791b0b9e"]]
[:place/by-id 17592186045425]}},
:om.next/tables #{}}
Is it true that a call to (om.next/reconciler ..)
returns an object which -- when dereffed -- returns the current state of your application?
This seems true when messing around in a REPL
@adamfrey: because I don’t know how temp-ids work … I haven’t used them yet so I just remove symbols
@adamfrey I also am using tempids, I'll take a look at see what comes in via the response and let you know
If I have a remote mutation for (om/transasct! [(some/mutation) :update/key])
. Should I expect the :update/key
be updated after the mutation call is complete?
@jlongster: there was/is (???) something weird with context that makes me stay away from it. Something with shouldComponentUpdate, if a component higher in the tree returns false, context doesn't re-render deeper down. You're probably aware of this, but I find this introducing a lot of fragility...
@george.w.singer: yes that's pretty much it, you get some extra stuff too like :om.next/queries
and :om.next/tables
.
The tables
is a set of the keys in your atom state that represent ident
- 'tables'/parts of your global state atom.
@tawus: in general if that's the desired result, you should implement your parser that way. Don't expect anything to happen out of the box.
@iwankaramazow: it also seems that the reconciler has normalized app state data, while the actual app-state doesn't retain the auto-normalized data
Question: when a component makes calls to (om.next/query ..)
via IQuery
, is the env
that is passed to the parser's reader function set to (i) the root app state, or (ii) the component's local props?
app state
what do you mean by the actual app-state data?
from what I understand there's a single source of truth: the reconciler
I mean the root app state fed to the reconciler (which might have been mutated through the reconciler)
or the initial app state?
You decide with your reads how the components get their data from the normalized format to the tree form that components use.
I mean what the components get fed when instantiated via (om.next/factory..)
Is THAT what is passed to env as the :state key?
I mispoke
(om.next/factory ..) returns a factory function that is fed with props
And when you feed that factory function with props
Does the env draw from those props to source its :state key?
in the parser's reader function?
the initial-state gets merged in the beginning
after that you can discard it
everything comes from the reconciler
What's weird is that I see in lots of tutorials (query ...)
declarations that draw directly from props.
example?
Are the props just fed in to immediately render a value?
and then the (query ...)
call is to signal "these props are stateful, and might change?
Example: https://github.com/omcljs/om/wiki/Components,-Identity-&-Normalization
The root component (the one the query composes to) gets fed props by the reconciler, from there it is up to you to split the right data into props for non root components.
probably the
[(points/increment ~props)]`
props is an arbitrary name here
it just means it gives the current value for name & points to the mutation
in fact you could only pass the :name
, look the :points
for that name up in the state (from the env), and update it
is there a convenience function to easily merge in an existing db state with a diff db state, or do I have to manually update all the links?
what do you mean with diff db state?
without overwriting existing stuff?
say I have :task/list [:task/by-id 123] :task/by-id {123 {:id 123 :name "I am a burden"}}
and I use tree->db and to create a new task and end up with :task/list [:task/by-id 456] :task/by-id {456 {:id 456 :name "I am a burden too"}}
if this is local only, yes.
If you merge incoming remote data, then you can use (defn custom-merge-tree [a b] (if (map? a) (merge-with into a b) b))
and then pass on your reconciler :merge-tree custom-merge-tree
I started out by putting my navbar on the server 😂
just fetching a list of nav-items that gets normalized on the client with idents
I am doing something pretty simple first too. Making something to track my activities, only added difficulty is I am using a query with recursion.
{:name :some-task :should-take (minutes 5) :sub-tasks [{:name "something that has to be done during the task" :should-take (minutes 2)}]}
hoping to somehow use web audio to bug me to track the tasks so I can get better at estimating tasks.
check this out if you want an example https://github.com/awkay/om-tutorial/blob/master/src/main/om_tutorial/client_mutation.cljs#L31-L43
that mutation just swaps
everything on the local state
In the "Components, Identity, and Normalization" tutorial (https://github.com/omcljs/om/wiki/Components%2C-Identity-%26-Normalization), there is a call to (query [this] '[:name :points :age])
within the Person
component definition. Yet there is never a parser read function defined which takes any of those keys (`:name`, :points
, :age
). Isn't this a problem?
Why isn't* this a problem?
Because only the root key read gets called, after that it is up to the read function to call the parser recursively if you want to split up your read.
(defn get-people [state key]
(let [st @state]
(into [] (map #(get-in st %)) (get st key))))
this functions makes sure everything gets read
As @bbss says, only the root keys get read, in this example :list/one
:list/two
The parser dispatches on :list/one
:list/two
As you can see in the tutorial, they both return {:value (get-people state key)}
the function get-people
makes sure every key gets read
for :list/one
you'll first look up (get st :list/one) with get-people
that gives you
[[:person/by-name "John"]
[:person/by-name "Mary"]
[:person/by-name "Bob"]]
then the map
function applies #(get-in st %)
to the vector above
basically it'll do (get-in st [:person/by-name "John") (get-in st [:person/by-name "Mary") (get-in st [:person/by-name "Bob")
and st contains
:person/by-name
{"John" {:name "John", :points 0},
"Mary" {:name "Mary", :points 0, :age 27},
"Bob" {:name "Bob", :points 0},
"Gwen" {:name "Gwen", :points 0},
"Jeff" {:name "Jeff", :points 0}}
the return value gives you {:name "John", :points 0}
for [:person/by-name "John"]
in the tutorial the query '[:name :points]
is redundant
it doesn't get used
So when (query ...)
is called
does it automatically send a read request to the parser?
And then -- in this case the parser says "I already have the information about that read, and I got it another read, so here is the value you're looking for"
@iwankaramazow: Just saw the https://github.com/awkay/om-tutorial link -- good stuff! I wish I knew about this before. Perhaps add to the wiki? (maybe it was there but I didn't see it...)
@thiagofm: yea that tutorial is a goldmine 😄 Everything you need is in there I'm not in charge of the wiki...
@george.w.singer: the reconciler will send the query from your Root Component through the parser
it's up to you to pick that query apart
the top level keys get recursively parsed though
there's nothing shady going on here, it's basic recursion
you might want to check out https://awkay.github.io/om-tutorial/#!/om_tutorial.E_State_Reads_and_Parsing
@iwankaramazow: do you think it's wrong to query something inside a mutate method? I have a case where it my mutation query depends on other attributes, which are really about another component
Most mutations happen based on some attributes (that might be or not be available at a component)
For intercomponent comunication I use graph-style queries
I could still query this data in the component level, but this would be just passed down to the mutation
a la 'thinking with links'
that's the approach I'm doing now
but the state is available in your env at the mutation in the parser
so you can get it from there
mutations have to be side-effect-free though
not sure if deref'ing your state is pure
I'm making a simple game, so let's say the player can buy a bunch of upgrades. To display his total money he made in the current tick/second, I can either query this data in the component(What he bought isn't getting displayed in that component or is anywhere related) and pass it to the mutation that does this calculation and updates the total money, or query inside the mutation. Querying inside the mutation kind of help, as it's pretty clear what's happening, but yeah, dunno if this is functional 😞
@iwankaramazow: what do you mean by side-effect free? I'm only transacting inside it, so it's like a side effect, no?
given the same arguments you should always have the same result
a simplistic example, if you (println "test")
that isn't pure
inside a mutation
for all we know your computer may crash and we don't see "test"
transact inside a mutation?
no, the thunk has to be side-effect free at :action
in your parser
@iwankaramazow: an example is better https://github.com/thiagofm/haxlife/blob/master/src/cljs/haxlife/data/query.cljs#L67
you probably shouldn't transact inside a mutation, I'm not sure though
ah wait
that's datascript
yea that's valid
Yes, in the tutorial here: https://github.com/omcljs/om/wiki/DataScript-Integration-Tutorial he transacts
looks pretty ok to me
but I'm no expert
But yeah, I think I kind of get it now. It's a better idea to query stuff in the component and have in the mutation perhaps only the transaction(and some pure function call if that's the case). Otherwise I'm not passing arguments to that mutation and it wouldn't feel side-effect free or pure, as by passing different arguments, i get a different result transacted everytime.
{:action (fn [] (let [[id total] (first (d/q '[:find ?id ?e :where [?id :lambdas/total ?e]] (d/db state))) per-second (ffirst (d/q '[:find ?e :where [_ :lambdas/per-second ?e]] (d/db state))) next-second-total (lambda-coins/next-second per-second)] (d/transact! state [{:db/id id :lambdas/total (+ total next-second-total)}] :values)))}) For example here I query [id total] and per-second. This could've been done in the component side
apart from the side-effect-free stuff (which I'm totally not sure about) , it's indeed a better idea to keep your mutations only about transactions
pure or side-effect-free is only about 'given the same input of a function, you always get the same output'
the input for that function may differ, but the result is always the same for a given input
I'm totally not sure if a read of your datascript db may happen in a mutation
someone should shed some more light on this, help? 😄
I wonder what (d/transact!) would return, but I guess if I send all the needed parameters to update the value needed, it's would have less side-effects
Makes sense to query this in the component. I'm still wrestling with the whole concept. Before I was using reagent and even though you can probably avoid all the side-effect you want if you write things well, my code was full of state
Om next seems to take this idea of side effect-free to another level. The idea for example to my root component have all the necessary queries from the children components was neat, but took me some time to figure out why it was like this
I've never used Reagent
side effect-free is all about making state management more predictable and reliable
this is a huge bonus when your state is complex
David Nolen had some pretty awesome revelations, when designing Om 😄
@thiagofm: I guess you don't use set-query anywhere? I'm interested how people make that work with datascript
hi guys what is the proper way to use om computed
. What I'm trying now is
(om/computed props {:text "something"})
(prn (om/get-computed this))
I got nil in return(defui Component
Object
(render [this]
(let [computed-props (om/get-computed this)]
(dom/div nil "stuff"))))
(def component (om/factory Component))
(component (om/computed props {:text something}))
@nxqd: that should do it
@danielstockton: I just have to use set-query in my main compontent, example: https://github.com/thiagofm/haxlife/blob/master/src/cljs/haxlife/components/window.cljs#L35
And then I pass om/props to children components and have the query on each separate component
I don't know if this is right, but after talking with a couple of people from here this is how I was able to make it work
This is also my first take on om next, I've decided to use datascript from the beginning so I can learn how to query datomic while I write this project. After I knew about that get-query usage I feel like I'm able to do whatever I want regarding to that, now I'm more like reflecting on how to use it right.
This is slightly different, that's get-query not set-query
If you're updating your queries in runtime with set-query!, you'll eventually discover that om doesn't work with datascript unless you make some modifications
om sets a ::queries key in the app-state and it expects the state to be an atom
I'm in the same position as you, I'd rather learn datomic/datascript than worry about normalization
@danielstockton: there isn't much about normalization ;) om/db->tree
takes care of the heavy lifting 😄
@danielstockton: oh, I get it. Yeah, with datascript I don't think I need to use it
@thiagofm: how do you implement routing?
you might want to, for example it seems like the preferred method of routing is using set-query!
although there are alternatives
im using union queries for now
@iwankaramazow: true, but i like that om promises to separate state from everything else, which isn't quite true at the moment
and i like datascript, you can do some powerful stuff that would be harder to implement with atoms
@danielstockton: never thought of it that way, but I see what you mean
i think it will change in future, it's not hard to make set-query state agnostic
I plan to experiment with datascript once this side project is pretty much finished
and provide a default implementation for atoms
are there any other problems with datascript atm?
in relation to Om.Next*
i think that's the main one, there are other pros/cons
tempids is another con, you have to either have a separate attribute which you can update or delete temporaryclient-side datums and re-transact datums from a remote
and i think a pro is that deletes are easier, i don't have to worry about deleting from multiple places
dunno if tempids is a real con, just requires a different approach
Interesting
the way I'm handling deletion now is in one place
I just delete the reference in a list for example
that is, the entity keeps existing in the :item/by-id
portion of my state
right, in case its referenced elsewhere
and it exists indefinitely?
or you have some cleanup methods
for the moment most code is 'cave-men-style', no cleanup methods
ps: do you have a Datomic backend along the datascrip?
yeah, im just toying also, things will become clearer as more people create serious projects with it
yep, datomic backend
so i send the datums from the server and just transact them, it makes some things easier
so the integration datascript/datomic is pretty smooth?
so far but as i said, just toying at the moment
might be issues i haven't come across yet
having datomic also simplifies the parser, you can pretty much take the om query and use it as pull syntax
Definitely, I switched my sql database to datomic
and I have to say I'm pretty amazed about how much less work I have to do...
datomic seems great for 95% of typical use-cases and i imagine it's easy to write parsers for the other 5% that you might want to store elsewhere
reports or whatever that is
The only problem is I can't even bring this stuff to my team at work
Clojure(script) & datomic is too much of a leap for most people
@iwankaramazow: I'm not doing routing and I just have intentions of doing just a couple of requests to the backend, most of the game is client side
Yeah, I also don't work with clojure, mostly ruby, mutable stuff... There aren't many jobs, it's hard to find one
datomic is actually a perfect fit for my work i think, i just dont have the confidence to fully push it yet which is why i want to experiment in toy projects
I find datomic very awesome It's great to be able to use datascript and learn a bit more about querying on it
@iwankaramazow: yes, context makes shouldComponentUpdate
more ambiguous, but only if the context ever changes. Context is great for exposing singletons throughout the whole app, and redux does that to connect everything to the store. We should be fine using it to connect the reconciler and indexer because you can't "switch them out" in the middle of your app
or you could always return true
in sCU if the context has changed at all, thus everything would rerender, but we should be able to just ignore context in there
@iwankaramazow: hmm, I think I can use (om/computed)
to store computed values ( cache ) while working within the component not passing from outside.
@jlongster: thanks for the clarification, I had a lot of problems with Redux trying to implement int18n trough context.
@nxqd: any luck yet?
@dnolen: Working on a support VCR viewer for app state history. I'd like to include client timestamps in the history. Wondering if you would accept a PR for tacking a timestamp onto the app-state entries in history as metadata?
I have tempid migration working but I need to generate idents that map to a local table, like [:transaction/by-id 1]
. How is the server supposed to know what the local id key should be for that id?
Is my bit understanding of om.next queries correct? Here it is: 1. Only the root query actually gets read by the parser; the results of this root query read are then fed in through the props of its child components. 2. The non-root component queries -- known as "fragment queries" -- aren't actually read by the parser; however, they are used behind the scenes for properly re-rendering your UI when state changes. Is this correct?
@george.w.singer: the non-root queries are still read by the parser, but it's up to you if you recursively call the parser, use db->tree
to execute the query, or do something else
You mean place db-tree
within your read
function?
"Om only looks for the query on the root component of your UI! Make sure your queries compose all the way to the root! Basically the Root component ends up with one big fat query for the whole UI, but you get to reason about it through composition (recursive use of get-query). Also note that all of the data gets passed into the Root component, and every level of the UI that asked for (or composed in) data must pick that apart and pass it down." -- https://awkay.github.io/om-tutorial/#!/om_tutorial.E_UI_Queries_and_State
That's where I got (1) and (2) above.
yes just the root query is the only thing passed to the parser, and it executes the whole entire query
So the child queries don't actually have any tangible effect when your app starts?
They are just using the props fed into them by the fat query read from the parent?
Ok, last question on this 😀 : Why call them "props" if they are stateful?
The child components get fed their queries from the root component, but that data is subject to change later on
Is it just a carry-over from react to refer to them as props?
they are still just props fed in from something, whether it's the parent component or the indexer doesn't matter
@george.w.singer: the point is to maintain the semantics that the root component is a function from props -> render tree
that the root of the application is stateful doesn’t have any bearing on the root component acting more or less as pure function
👍 Thanks 🙂
@jlongster: just curious, but why no uuid?
@iwankaramazow: not sure what you mean
'How is the server supposed to know what the local id key should be for that id?' Is this a normal use case?
it seems logical to generate unique random id's, merge those back in
om takes care of the rest ?
(I might have misunderstood your question..)
if an ident is [:foo/by-id 1]
and I add a new item, I add [:foo/by-id (om/tempid)]
and then later replace it, but for it to be replaced the server has to return an ident with the same key like [:foo/by-id 2]
I mean, the client can send the name of the key to be used as well, but all of the examples I've seen hardcode something like :db/id
Ah ok, good point
I'm just starting with tempids, I'll report back if it works
from Tony's tutorial: On the server, convert tempids to real IDs. Return a :tempids map from the server. Keys are tempid ident, the values are the new real ID idents.
but right now I have to hardcode :transactions/by-id
in the ident, but there are other idents like :accounts/by-id
that need to work too
Just looked at the copaste example
seems sending them as params looks like the only solution
@anmonteiro: reviewing your PR, I’d like to do some renaming here to make the code clearer - I can’t say I like how I named things before and now that the logic is going to get a little more sophisticated my bad naming choices are becoming apparent.
@dnolen: perfect. I am also not happy with the naming
but I couldn't really find better alternatives
you know, computer science & naming for one, not being a native speaker for seconds
so get-local-query-data
should also be get-instance-query-data
?
just saw your comment
or actually maybe since we’re already using component
to always means instance
we can just be more consistent about it
I noticed there's a tempid?
available in cljs om.next but not in clj om.next.server...
Totally not a priority, but might be nice in the future
Oh nevermind
:face_with_rolling_eyes:
@anmonteiro: also get-local-query
is a bit strange since it also checks component?
, I’m not sure I understand the docstring.
@dnolen: the docstring definitely needs changing too
basically this is the old get-query
which falls back on the class
I meant local in the sense that it doesn't leverage the indexes
@anmonteiro: ah, and the new get-query
uses the indexer
but then again, local
doesn't really mean anything 😛
@anmonteiro: in the dev cards bit you invoke get-local-query
is this intentional? Just testing something?
@dnolen: unintentional. remains from a previous version where I thought get-local-query
had to become part of the public API
I've also noticed that I'm calling it unnecessarily in the build-index*
function.
needs changing
@anmonteiro: k I’m leaving comments on all these cosmetic things first, then let’s do another PR and I’ll review a second time (good to read over this at least twice on my end)
@dnolen: sounds good
@dnolen: I didn't understand if you had finished commenting or still going through it
@anmonteiro: so it seems cascade-query
does the missing work of updating all the query templates?
@dnolen: except the case where the query is changing in a union which we treat differently in build-index*
because there might or might not be a class that holds the union query
@anmonteiro: hrm, in general you need a class to hold the union query though
@dnolen: I've tinkered with cases where I don't, and it doesn't seem like a problem
e.g.:
(defui Root
static om/IQuery
(query [this]
[:app/route {:route/data {:tab1 [:a :b] :tab2 [:c :D}}]))
it's used a lot for e.g. routing
where we don't want a specific Router
class or something
I've toyed with both cases
exactly
there'll be no idents
because each "page" is only shown once at a time
@anmonteiro: so union-keys
param in cascade-query
is to ignore these cases?
@dnolen: all union cases
because e.g. in the Unions tutorial, DashboardItem
and Post
have the same data-path
so when changing a union query we want to eliminate the union key that leads to the path because it's not there
I don't think I'm explaining myself well enough
build-index*
will keep the extra union keys in the path it computes, and the rendered components skip the union key in their data paths
that's more like it
@anmonteiro: k comments done - mostly boring stuff - send me a new PR I’ll give a second read and we can merge it
@dnolen: cool, thanks, but I don't think I'll get around to it today
@anmonteiro: that’s fine, no rush
@dnolen: you mentioned changing dp
to key-path
should the indexer key also be changed? data-path->components
to something else?
@anmonteiro: hrm, we should keep the language consistent whatever the case
OK so changing to data-path
instead of key-path
@dnolen: actually got around to it today, you want a fresh PR, right?
@anmonteiro: cool! yes please
incoming