This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-02-15
Channels
- # aatree (23)
- # admin-announcements (13)
- # announcements (3)
- # beginners (49)
- # boot (50)
- # braid-chat (1)
- # braveandtrue (37)
- # cider (72)
- # cljs-dev (25)
- # cljsjs (6)
- # cljsrn (37)
- # clojure (78)
- # clojure-berlin (8)
- # clojure-greece (1)
- # clojure-ireland (2)
- # clojure-madison (14)
- # clojure-new-zealand (2)
- # clojure-poland (10)
- # clojure-russia (149)
- # clojured (2)
- # clojurescript (49)
- # community-development (6)
- # core-async (37)
- # cursive (1)
- # data-science (1)
- # datomic (30)
- # emacs (4)
- # euroclojure (1)
- # funcool (1)
- # graclj (1)
- # hoplon (17)
- # jobs (2)
- # jobs-rus (45)
- # ldnclj (6)
- # mount (12)
- # off-topic (124)
- # om (270)
- # onyx (131)
- # parinfer (70)
- # perun (2)
- # proton (168)
- # re-frame (32)
- # reagent (29)
- # ring-swagger (8)
- # testing (9)
- # yada (39)
noob question if you don’t mind
I am working on my first om app, I am using a pattern which I sort-of stole from the om Basic tutorial
my app state looks like
(defonce app-state (atom {:page :filter}))
my root component is
(defn root-component [app owner]
(reify
om/IRender
(render [_]
(dom/div nil
(dom/div #js {:onClick (fn [] (swap! app-state assoc :page :filter))} "Search")
(om/build omp/page app)))))
and my site navigation is just onclick handlers that swap!
the :page in the state atom
this works in simple cases
however for rendering one page, I want to pull some data from an ajax call and set this as the state of the component, and then render it
So going off the om docs I have added a g-loop in the IWillMount/will-mount
method of the component that will receive the data, that reads the data from a core.async channel, and calls set-state!
the problem is, with my technique of changing views by swapping the :page in app-state, the IWillMount gets called every time I load a page, not once as I suspect is expected
So, am I doing something obviously wrong here?
@clumsyjedi: if you are learning or your app does not have a very soon deadline I coud start with om.next insted of om.
any chance the replace
internal function could be renamed? While going through the full-query
code it took me a long time to realize that the replace
it uses is that one, not the core library replace
@jlongster: I think only David can answer that, but I don't think it's a priority just for readability's sake
sure, not a priority but shadowing core functions makes it hard for new people to go through the code
I've seen it done in Clojure quite a bit but I'm not a fan of it... but it's not a big deal
in that case there's also force
and var?
Has anyone tried boot-cljs-test
with Om Next yet? My setup is probably incomplete as I get a ReferenceError: Can't find variable: React
error when running things.
@jannis: I'm not a Boot user, but does that task use Node.js to test?
I ran into a problem the other day writing tests against Node.js
basically I couldn't rely on any React.DOM dependencies
because it would throw a cryptic error saying React wasn't found
i.e. anything that imports om.dom
couldn't be required by my tests
npm install react
solved it
but this was for Node
what I did as a permanent solution was move the functions I wanted to test into a namespace that doesn't require om.dom
Right. I doubt that's feasible in my case - I'm testing a macro that sits on top of defui
.
can you not test it in clojure?
Not really. It generates ClojureScript code that does (defui ...)
and defines a few functions that I then want to test.
ok, second try:
can't you test them at the repl?
without phantom
I've written a few tests for Om that use defui instances
and don't rely on a DOM
maybe this could help?
it could work if your defui
s don't have a render function
But to get a browser REPL up I'd have to start a browser manually. I'm looking for a way to automate these tests.
The tests I've linked don't use a browser repl
they're automated
I've added cljsjs.react
as a dependency and am requiring it in my .cljs
test. Now I can run it in phantom and it works.
that was simpler
I'm not sure why requring om.next
doesn't pull React in when using phantomjs... but ok.
om.next
doesn't require cljsjs.react
only om.dom
I don't think it does
However, I dropped the dependency on cljsjs.react
and instead required om.dom
and it still works. So that's better
if you're using defui
without render methods, you need to pull in cljsjs.react
, I suppose
om-dom
pulls both react and react-dom
because you didn't pull om.dom
at first?
but they pull cljsjs.react
@jannis: I don't think so
you'd probably get some error about the reconciler being missing
That actually works. But what I get back from (some-component {:foo :bar})
is something fairly incomplete: #js {:$$typeof 60103, :type app-macros.view-test/ViewWithProps, :key nil, :ref nil, :props #js {:omcljs$value #object[om.next.OmProps], :omcljs$instrument nil, :omcljs$shared nil, :omcljs$path nil, :omcljs$reconciler nil, :omcljs$depth 0, :omcljs$parent nil, :children nil}, :_owner nil, :_store #js {}}
It should have .-omcljs$isComponent true
, an ident
function and and a few JS object methods.
That's the code generated by my macro, instantiating happens via (view-with-props ...)
: https://gist.github.com/Jannis/e695636cb71a3c14d1c7
@jannis: you're printing out the React instance
(.. view -prototype -omcljs$isComponent)
should be there
whatever you're printing out
my bad
instances don't have a prototype
__proto__
that should work
(.-prototype (type this))
it's not omcljs$isComponent
but instead: om$isComponent
and this should be there: (.-om$isComponent this)
True, it's om$isComponent
. But it's not set on the instance or it's prototype or its type's prototype.
I'm seeing it
I wanted to confirm that for you but my builds just went crazy
I'm having trouble getting a basic transact!
to work. I'm not supplying any additional reads to perform, so all that should happen is that the component I transacted on is re-rendered, correct?
@jannis: (println (.-om$isComponent this))
=> true
it is re-rendered, but for some reason the data coming from props is nil
. I know this is vague, trying to figure out a simple way to show my code
@jlongster: I've got to run now, but my first suspicion would be you're doing something wrong in the parser
@anmonteiro: I figured it out a bunch of problems I think. I need to use computed props, as well as pass down data properly. however, now it looks like the component I transacted on isn't rerendered (I should see a console.log from the render
function). Hope this code is readable enough: https://gist.github.com/jlongster/757ab6be90d0643bf4a3
@jlongster: I see nothing wrong with the code that would cause a missed render
@tony.kay: side question, do I have to pass down props directly that correspond to queried data? i.e. query is [:foo :bar]
, (om/props this)
should be an object like {:foo 1 :bar 2}
yes, that and I was also passing it as {:transaction transaction}
instead of just passing the transaction object itself
but I see now that Om needs to know how to re-render the component with the right props
yes, and it can rerender a child without the parent, which is why computed...so it can cache the parent's out-of-band generated data (like callbacks)
I posted a longish question about om.next/db->tree on stackoverflow, so I won't repeat it here. Thanks in advance for taking a look. http://stackoverflow.com/questions/35415756/om-next-tutorial-components-identity-normalization-om-db-tree-usage
@alpheus: because if you do that the shape of the data does not match the shape of the queries
tree->db
takes data that should have been returned from running a full query that came from components, and normalizes it based on the component relationships
might help to get familiar with components and queries generally first, if you aren't
@tony.kay: you have a page on pathopt (https://awkay.github.io/om-tutorial/#!/om_tutorial.I_Path_Optimization), are there any catches? I'm assuming there are if :pathopt true
is not the default
@jlongster, thanks, I'm working my slowly through the tutorials for the second time and getting a better understanding each pass.
@jlongster: correct. path optimization requires you make your parser handle requests for idents directly
You're basically saying "If a component has an ident, you can try to run the query straight from that component to rerender it (instead of root)". Basically, you'll want to add a debug statement to your read function to see what question you get asked, and make sure you respond to it. If you return nil from a path optimized query, then Om will back off and run it from root.
@jlongster: If you care to beef up that part of the tutorial after you get it working, please feel free! PR encouraged
I have some notes about how you can run a full-stack Om app inside a devcard as well, whjich would make writing such a thing easier
@tony.kay: definitely will help flesh some of that out at some point, as well as writing some more docs for beginners. not at that point yet, but eventually I'll understand things well enough to do so
my weakest understanding is mutations... I'm still confused about this scenario: you have a list of items, and you query for all of them and render them each with an Item
component that has a [:field1 field2 ...]
type query, composed with the top-level component that has a query like [{:items ~(om/get-query Item)}]
. If an Item
calls transact!
to mutate itself, it looks like the entire root query get runs again which returns all items, is that right?
maybe not the entire root query, but the composed [{:items [:field1 :field2]}]
query
@jlongster: I think that's the case right know
I println
-ed a read method in my parser, seems it gets reread quite a lot
correct me if I'm wrong
@iwankaramazow: and read
only gets called for a local read, it doesn't go through all the remotes when re-reading after a transact!
. my code would have actually worked if it did that, because it would be re-queried everything from the server.
but pathopt
is definitely what solves this so that it only re-queries and re-reads that specific item
yea, the remote scenario seems to always be 'what should be expected'
although most of my parser functions are like
(defmethod read :navbar/items
[{:keys [query state]} key params]
(let [st @state]
(if (nil? (get st key))
{:remote true}
{:value (om/db->tree query (get st key) st)})))
but I'm not sure why read
is only called locally for transacts, but I'm sure that's intentional
if you send a transact to a remote, doesn't it return the parts of the query that needs to be re-read?
=> therefore we only need a local read when the result arrives?
(thinking out loud)
I just run a query against the database: https://gist.github.com/jlongster/84e2e1ab2cb8d1db2002
I was talking about remote transactions
thought the gist was on the frontend part
what is :value
used for in mutations? I read somewhere about that but I can't find it anymore
Someone here on Slack said it was for 'documentation purposes' only
Not sure if that's true, seems pretty redundant
I'll check it out with some code
it seems like the idea is that you're supposed to do the local mutation as well as send it off remotely; but I wonder how you would re-read remotely if for some reason you don't want to implement the mutation locally
hmm, if you set the read function that corresponds to the mutation to :remote true
, does that trigger a re-read on the server?
@iwankaramazow: hm actually there may be a bug in my code. In another mutation I have, (om/transact! this '[(mutation) :items]
does read :items
both locally and remotely, so I may just be going down a rabbit hole
it actually is already remote, but shouldn't matter because I'm logging at the top-level read so I should see all calls to it (remote and local)
if you change the query to something like (om/transact! this [(route/update {:url ~href}) {:route [:url]}])
In your case (om/transact! this '[(mutation) :items {:field-I-want-to-re-read}]
oh nevermind
misread
although that might be it
you need to return a map on the re-read portion
currently you only have :items
(om/transact! this '[(mutation) { :items } ]
might be it
line 40
@iwankaramazow: I see what you're saying, the query needs to be right, but I think for my data my code was OK. I just figured it out though: it is running that query remotely, but it groups all the remote queries together and sends them at once I don't know why it wasn't actually working before, but now that I understand things better things seem to work as expected
@iwankaramazow: ah, I meant for mounted components; thank you though
specifically I'm trying to figure out why the wrong mutation is happening, and I'm setting up the component for mutation in the componentDidMount portion of the lifecycle
@futuro: any code I can review?
That is really close to my original, but since then I've gone so far as to have duplicate, differently named components, factories, and mutation functions, as well as duplicating my TimePicker js lib, giving it a distinct name from the original
I've also attempted having one of the components not have a componentDidMount lifecycle specified, and attach it from my FF console window, and the same behavior happens
dbkey returns both :newevent/beginning & :newevent/ending?
@jlongster: transact!
will only send ‘transformed’ reads in the tx to the remote, add-root!
, set-query!
(without any reads) will call parse with a target on the whole thing
and you probably want to understand the transform-reads
function, or make your reads idents
@futuro: and when logging the dbkey it still returns the wrong mutation?
even when both components have completely separate mutation execution paths, i.e. no shared code, it always calls mutation the mutation with whichever component is registered first
f.e., if I create a comp with :dbkey :newevent/beginning, then another one with :dbkey :newevent/ending, every mutation, no matter how I've created the second component, is for :newevent/beginning
transform-reads
101 … a simple read, :items
, is looked up in the indexer to see what components have a top level prop :items
, and om.next will then focus each components query on that key and send it to the remote … if none have a top level :items
then that read is removed from the transaction … idents are skipped and sent as-is (but looked up during the reconcile to re-render)
@futuro: so it seems like it's a 'shared' componentDidMount?
well, this happens even when I've created two separate components, no shared code, different names, different factory functions
that might be the case
the root query composes everything together
maybe the mutations get both composed in the same 'tick'
could that be the case?
Maybe, but I've also had one component use it's DidMount lifecycle to construct it's mutations, and then used the dev console with the other component (plain input, no timepicker attached), to attach the timepicker to it
also, js/TimePicker.bindInput attaches a click event listener to a DOM element, which fires off code internal to the TimePicker library, so should't, I think affect it
though I've also copied the TimePicker library so I have two, separate instances of it available, and used one instance for one component, and another instance for another component
Hmm, sorry don't think I can help you without debugging the actual code
and if they are not currently mounted by any components (`:prop->classs`) they’ll be filtered out
@iwankaramazow: thanks regardless
idents are passed through as is allowing you to focus yourself, {[:item/by-id 1] [:key1 :key2]}
my knowledge of how this works is actually more for previous versions, so I need to confirm the current details
so if you have a transact!
and include a simple key for something not mounted, it won’t be sent to the remote
also, prop->class
only indexes ‘top level’ props, e.g. [{:current-user [{:items [:key1 :key2]}]}]
will prop->class
:current-user
for the component
last detail (sorry for spam!), if you transact!
with a read of :item/description
, it will put a read for every :item/description
you have which may not be what you want … you only want the specific :item/description
to be sent to the remote for re-reading … again, you can focus yourself on an ident to be explicit about this
@hueyp: thanks! slow to respond right now but that looks like a good thing to study, haven't looked at that yet
I spent the last few days diving into it — but mostly with our current version and then looking to see if any changes in master. I need to really re-test on master to make sure I’m not missing stuff by just looking at code.
but yah, we’re finding it super important to understand the mechanics of transact!
in order to keep remote and local data in sync
Is React's context
available in Om?
I haven’t tried, but there is also a :shared
data you can provide to your reconciler and read from every component
I’d think you could use context
fine since its just react components, but don’t know for sure
https://github.com/omcljs/om/blob/master/src/main/om/next.cljs#L482 / https://github.com/omcljs/om/blob/master/src/main/om/next.cljs#L1753 for shared infos
Interesting
thanks!
anytime, I saw that in the reconciler code, and then it being used in these examples as a kind of shared message bus : https://github.com/stevebuik/om-next-ideas/blob/master/src/cljs/om_next_ideas/app/views.cljs#L19
Why doesn't Om use context like redux for example to pass the props down automatically to the IQuery
components?
something I have been asking myself lately
I haven’t used redux, but basically a higher order component that talks to the ‘store’ directly?
the 'store' is accessible in those higher order components through context
yah, so you’d need the parent to pass some pointer information to their children … this is exactly what Relay does btw
indeed
like if a component query is [:id :name :age]
, it needs to know what ‘path’ in the store to run that query against
so when a Relay parent passes props to its children, its just passing that path and the children hit the store directly
what's your verdict in relay in general?
in relation to Om Next for example
I’m a fanboy … I think it doesn’t cover all use cases and you sometimes need to pair it with a local store, but the remote -> local data story is amazing
but if you end up with something that doesn’t fit its model I imagine its really a take it or leave it kind of thing
I struggle with om next and I’m not sure if its because I keep wanting to apply Relay concepts or what
how to compose queries … I might do something like this equivalent in Relay using om.next syntax: (query [_] (into [:id] (concat (om/get-query BookHeader) (om/get-query BookDetails))))
like I have a book component, and it just wants the id, but then there is a book header and book details
I think instead you do something like [{:header (om/get-query BookHeader)} {:details (om/get-query BookDetails)}]
… and then filter out :header
and :details
in your parser … or use om/process-roots
or something … I don’t really know
Is the graphql -> your backend queries a smooth story with relay?
Relay puts a few requirements on your GraphQL server around identity / collections, but conceptually you still model your data as a graph with a few entry points … e.g. if you draw a entity diagram (I think thats the right term?) you model that, irrespective of the UI
like if a field makes sense in the UI, I think facebook has ‘like_setence’ or whatever, you put that in, but not ‘header’ and ‘details’ and stuff like that
k, thanks for input
so if I want a ‘callback’ … did this mutate fail / succeed / etc … you expose that in state someplace … but that is really me just guessing on redux 😜
and same thing with routing … is the redux store, or the url, the one in charge in routing … I want to see how redux impls have looked at handling that
React Router is in charge of routing with redux 😛
there are simple bindings floating around which keep the url in sync with the store
yah, I think @jlongster has written about this? I have dutifully starred these, but haven’t read them yet 😜
yeah I wrote react-router-redux. if you use react-router, redux isn't exactly in control of routing, react-router does all the work and you use there APIs. but yes for error conditions etc everything must be explicitly stored in the state.
@dnolen: the PR I just sent your way covers a basic mistake I've been seeing of people implementing IQuery
but either returning nil
or []
@dnolen: perhaps don't merge it yet. This only covers the case of the root class, I just noticed
@dnolen: moved it from add-root!
to build-index*
, should be good to go