Fork me on GitHub
#om
<
2016-02-15
>
clumsyjedi01:02:32

noob question if you don’t mind

clumsyjedi01:02:09

I am working on my first om app, I am using a pattern which I sort-of stole from the om Basic tutorial

clumsyjedi01:02:34

my app state looks like

(defonce app-state (atom {:page :filter}))

clumsyjedi01:02:20

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)))))

clumsyjedi01:02:08

and my site navigation is just onclick handlers that swap! the :page in the state atom

clumsyjedi01:02:16

this works in simple cases

clumsyjedi01:02:57

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

clumsyjedi01:02:58

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!

clumsyjedi01:02:58

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

clumsyjedi01:02:11

So, am I doing something obviously wrong here?

geraldodev15:02:30

@clumsyjedi: if you are learning or your app does not have a very soon deadline I coud start with om.next insted of om.

jlongster15:02:15

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

anmonteiro15:02:06

@jlongster: I think only David can answer that, but I don't think it's a priority just for readability's sake

jlongster15:02:39

sure, not a priority but shadowing core functions makes it hard for new people to go through the code

jlongster15:02:04

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

anmonteiro15:02:07

in that case there's also force and var?

jlongster15:02:28

yeah, that's true. it's not a huge deal, obviously you all can make the call

jannis15:02:25

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.

anmonteiro15:02:02

@jannis: I'm not a Boot user, but does that task use Node.js to test?

jannis15:02:16

Phantom in this case. Why?

anmonteiro15:02:39

I ran into a problem the other day writing tests against Node.js

anmonteiro15:02:01

basically I couldn't rely on any React.DOM dependencies

anmonteiro15:02:13

because it would throw a cryptic error saying React wasn't found

anmonteiro15:02:36

i.e. anything that imports om.dom couldn't be required by my tests

jannis15:02:56

Did you find a solution or workaround for it?

anmonteiro16:02:06

npm install react solved it

anmonteiro16:02:20

but this was for Node

anmonteiro16:02:44

what I did as a permanent solution was move the functions I wanted to test into a namespace that doesn't require om.dom

jannis16:02:35

Right. I doubt that's feasible in my case - I'm testing a macro that sits on top of defui.

anmonteiro16:02:07

can you not test it in clojure?

jannis16:02:11

Not really. It generates ClojureScript code that does (defui ...) and defines a few functions that I then want to test.

anmonteiro16:02:07

ok, second try:

anmonteiro16:02:12

can't you test them at the repl?

anmonteiro16:02:22

without phantom

jannis16:02:53

Yes, in a browser REPL it should work.

anmonteiro16:02:06

I've written a few tests for Om that use defui instances

anmonteiro16:02:12

and don't rely on a DOM

jannis16:02:16

Automated tests?

anmonteiro16:02:18

maybe this could help?

anmonteiro16:02:36

it could work if your defuis don't have a render function

jannis16:02:56

They don't

jannis16:02:48

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.

anmonteiro16:02:12

The tests I've linked don't use a browser repl

anmonteiro16:02:14

they're automated

jannis16:02:22

How do you run them?

jannis16:02:45

Oooh, problem solved.

jannis16:02:29

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.

anmonteiro16:02:30

that was simpler

jannis16:02:41

I'm not sure why requring om.next doesn't pull React in when using phantomjs... but ok.

anmonteiro16:02:14

om.next doesn't require cljsjs.react

jannis16:02:01

Of course not. But it does usually pull React in through externs, doesn't it?

anmonteiro16:02:15

I don't think it does

jannis16:02:35

I probably misunderstand how it works then.

jannis16:02:59

However, I dropped the dependency on cljsjs.react and instead required om.dom and it still works. So that's better simple_smile

anmonteiro16:02:08

if you're using defui without render methods, you need to pull in cljsjs.react, I suppose

anmonteiro16:02:03

om-dom pulls both react and react-dom

jannis16:02:42

Ok. I guess then the question is why it complained about React being missing.

anmonteiro16:02:54

because you didn't pull om.dom at first?

jannis16:02:12

Yeah, but the Om Next tests don't either.

anmonteiro16:02:32

but they pull cljsjs.react

jannis16:02:49

Oh! You're right, sorry. It makes sense now. Thank you simple_smile

jannis16:02:22

Is instantiating a component via a factory expected to work outside om/add-root!?

anmonteiro16:02:28

@jannis: I don't think so

anmonteiro16:02:45

you'd probably get some error about the reconciler being missing

jannis16:02:31

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 {}}

jannis16:02:07

It should have .-omcljs$isComponent true, an ident function and and a few JS object methods.

jannis16:02:21

Could be phantomjs but I don't have a clue.

jannis16:02:57

That's the code generated by my macro, instantiating happens via (view-with-props ...): https://gist.github.com/Jannis/e695636cb71a3c14d1c7

anmonteiro16:02:33

@jannis: you're printing out the React instance

anmonteiro16:02:06

(.. view -prototype -omcljs$isComponent) should be there

jannis16:02:44

Ah, of course I am.

jannis16:02:55

What's view in that case? The React instance?

anmonteiro16:02:37

whatever you're printing out

jannis16:02:18

.-prototype of the instance is nil.

anmonteiro16:02:22

instances don't have a prototype

anmonteiro16:02:29

that should work

anmonteiro16:02:05

(.-prototype (type this))

jannis16:02:28

Interesting. That's empty: #js {}

anmonteiro16:02:58

it's not omcljs$isComponent

anmonteiro16:02:04

but instead: om$isComponent

anmonteiro16:02:16

and this should be there: (.-om$isComponent this)

jannis16:02:55

True, it's om$isComponent. But it's not set on the instance or it's prototype or its type's prototype.

anmonteiro16:02:41

I'm seeing it

jannis17:02:53

I'll compare with a component created in a browser. Perhaps it's phantomjs this time.

anmonteiro17:02:46

I wanted to confirm that for you but my builds just went crazy

jannis17:02:56

No problem, I've got a call now anyway. I'll be back later.

jlongster17:02:58

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?

anmonteiro17:02:45

@jannis: (println (.-om$isComponent this)) => true

jlongster17:02:52

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

anmonteiro17:02:26

@jlongster: I've got to run now, but my first suspicion would be you're doing something wrong in the parser

jlongster17:02:57

most likely, thanks. I'll keep looking

jlongster17:02:35

@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

jlongster17:02:48

I'll keep playing with it, but let me know if there's something obviously wrong

jlongster17:02:56

oh, actually I bet this is a problem with my parser

tony.kay17:02:22

@jlongster: I see nothing wrong with the code that would cause a missed render

jlongster17:02:23

not returning the right props and shouldComponentUpdate returns false

jlongster17:02:35

ok, thanks for taking a look

jlongster17:02:24

@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}

jlongster17:02:40

ok, good to know, wasn't doing that before

tony.kay17:02:46

yeah, that would be bad simple_smile

tony.kay17:02:02

om/computed was the right answer (you found) for passing extra stuff

jlongster17:02:38

yes, that and I was also passing it as {:transaction transaction} instead of just passing the transaction object itself

jlongster17:02:50

but I see now that Om needs to know how to re-render the component with the right props

tony.kay17:02:30

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)

alpheus17:02:06

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

jlongster17:02:55

@alpheus: because if you do that the shape of the data does not match the shape of the queries

jlongster17:02:57

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

alpheus17:02:39

Thanks, I'll meditate on that for a while

jlongster17:02:29

might help to get familiar with components and queries generally first, if you aren't

jlongster17:02:09

@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

alpheus17:02:24

@jlongster, thanks, I'm working my slowly through the tutorials for the second time and getting a better understanding each pass.

alpheus17:02:26

*my way slowly

tony.kay18:02:30

@jlongster: correct. path optimization requires you make your parser handle requests for idents directly

tony.kay18:02:41

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.

tony.kay18:02:02

so it let's you selectively optimize

jlongster18:02:39

nice, thanks. I'm doing that now (logging each read request). Makes sense.

tony.kay18:02:53

@jlongster: If you care to beef up that part of the tutorial after you get it working, please feel free! PR encouraged simple_smile

tony.kay18:02:59

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

jlongster18:02:15

@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

jlongster18:02:01

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?

jlongster18:02:08

maybe not the entire root query, but the composed [{:items [:field1 :field2]}] query

iwankaramazow18:02:42

@jlongster: I think that's the case right know

iwankaramazow18:02:20

I println-ed a read method in my parser, seems it gets reread quite a lot

iwankaramazow18:02:02

correct me if I'm wrong

jlongster18:02:14

@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.

jlongster18:02:29

but pathopt is definitely what solves this so that it only re-queries and re-reads that specific item

iwankaramazow19:02:21

yea, the remote scenario seems to always be 'what should be expected'

iwankaramazow19:02:08

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)})))

jlongster19:02:46

yeah I still need to work through how my local & remote parsing functions interact

jlongster19:02:17

but I'm not sure why read is only called locally for transacts, but I'm sure that's intentional

iwankaramazow19:02:50

if you send a transact to a remote, doesn't it return the parts of the query that needs to be re-read?

iwankaramazow19:02:09

=> therefore we only need a local read when the result arrives?

iwankaramazow19:02:14

(thinking out loud)

jlongster19:02:13

I don't do that, should I? how do you do that?

iwankaramazow19:02:13

I was talking about remote transactions

jlongster19:02:46

that is remote, that is a mutation run on the server

jlongster19:02:56

or you mean the local ones marked as remote?

jlongster19:02:22

that just returns {:remote true}

iwankaramazow19:02:42

thought the gist was on the frontend part

jlongster19:02:15

what is :value used for in mutations? I read somewhere about that but I can't find it anymore

iwankaramazow19:02:56

Someone here on Slack said it was for 'documentation purposes' only

iwankaramazow19:02:07

Not sure if that's true, seems pretty redundant

iwankaramazow19:02:13

I'll check it out with some code

jlongster19:02:17

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

iwankaramazow19:02:57

hmm, if you set the read function that corresponds to the mutation to :remote true, does that trigger a re-read on the server?

jlongster19:02:04

@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

jlongster19:02:30

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)

iwankaramazow19:02:52

if you change the query to something like (om/transact! this [(route/update {:url ~href}) {:route [:url]}])

iwankaramazow19:02:37

In your case (om/transact! this '[(mutation) :items {:field-I-want-to-re-read}]

iwankaramazow19:02:10

although that might be it

iwankaramazow19:02:18

you need to return a map on the re-read portion

iwankaramazow19:02:29

currently you only have :items

iwankaramazow19:02:54

(om/transact! this '[(mutation) { :items } ] might be it

futuro19:02:25

anyone know of a way to see the lifecycle functions attached to components?

jlongster19:02:31

@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 simple_smile I don't know why it wasn't actually working before, but now that I understand things better things seem to work as expected

futuro19:02:53

@iwankaramazow: ah, I meant for mounted components; thank you though

futuro19:02:57

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

iwankaramazow19:02:47

@futuro: any code I can review?

futuro19:02:43

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

futuro19:02:28

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

iwankaramazow19:02:33

dbkey returns both :newevent/beginning & :newevent/ending?

hueyp19:02:35

@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

futuro19:02:56

dbkey is either :newevent/beginning or :newevent/ending

hueyp19:02:26

if you are using a remote you need to put reads in a transact! to keep data in sync

hueyp19:02:59

and you probably want to understand the transform-reads function, or make your reads idents simple_smile

iwankaramazow19:02:14

@futuro: and when logging the dbkey it still returns the wrong mutation?

futuro19:02:30

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

futuro19:02:28

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

hueyp19:02:37

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)

futuro19:02:39

and vice versa

iwankaramazow19:02:02

@futuro: so it seems like it's a 'shared' componentDidMount?

futuro19:02:39

well, this happens even when I've created two separate components, no shared code, different names, different factory functions

futuro19:02:08

and yet, all I can come up with is that both components are sharing data

futuro19:02:30

so my code has no shared parts, but maybe it's getting glued together by om?

iwankaramazow19:02:52

that might be the case

iwankaramazow19:02:08

the root query composes everything together

iwankaramazow19:02:26

maybe the mutations get both composed in the same 'tick'

iwankaramazow19:02:31

could that be the case?

futuro19:02:11

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

futuro19:02:27

which I would think would be completely out of band, but maybe not

futuro19:02:29

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

futuro20:02:11

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

iwankaramazow20:02:43

Hmm, sorry don't think I can help you without debugging the actual code

jlongster20:02:52

@hueyp: thanks, what do you mean by "transformed" reads exactly?

hueyp20:02:12

mentioned it in my next comment

hueyp20:02:25

but simple reads, like :items, are transformed into full queries using the indexer

hueyp20:02:43

and if they are not currently mounted by any components (`:prop->classs`) they’ll be filtered out

hueyp20:02:00

also {:items [:key1 :key2]} is transformed identically to :items

futuro20:02:02

@iwankaramazow: thanks regardless

futuro20:02:16

maybe I'll be able to create a small repro and file a bug

hueyp20:02:33

idents are passed through as is allowing you to focus yourself, {[:item/by-id 1] [:key1 :key2]}

hueyp20:02:15

my knowledge of how this works is actually more for previous versions, so I need to confirm the current details

hueyp20:02:06

so if you have a transact! and include a simple key for something not mounted, it won’t be sent to the remote

hueyp20:02:10

also, prop->class only indexes ‘top level’ props, e.g. [{:current-user [{:items [:key1 :key2]}]}] will prop->class :current-user for the component

hueyp20:02:16

in the version of om I’m using at least

hueyp20:02:15

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

jlongster20:02:50

@hueyp: thanks! slow to respond right now but that looks like a good thing to study, haven't looked at that yet

hueyp20:02:32

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.

hueyp20:02:50

but yah, we’re finding it super important to understand the mechanics of transact! in order to keep remote and local data in sync

iwankaramazow20:02:50

Is React's context available in Om?

hueyp20:02:02

I haven’t tried, but there is also a :shared data you can provide to your reconciler and read from every component

hueyp20:02:18

I’d think you could use context fine since its just react components, but don’t know for sure

hueyp20:02:14

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

hueyp20:02:23

I haven’t used it yet

iwankaramazow20:02:38

Why doesn't Om use context like redux for example to pass the props down automatically to the IQuery components?

iwankaramazow20:02:45

something I have been asking myself lately

hueyp20:02:38

I haven’t used redux, but basically a higher order component that talks to the ‘store’ directly?

iwankaramazow20:02:53

the 'store' is accessible in those higher order components through context

hueyp20:02:22

yah, so you’d need the parent to pass some pointer information to their children … this is exactly what Relay does btw

hueyp20:02:40

like if a component query is [:id :name :age], it needs to know what ‘path’ in the store to run that query against

hueyp20:02:57

so when a Relay parent passes props to its children, its just passing that path and the children hit the store directly

hueyp20:02:13

I need [:id :name :age] for this path my parent gave me

iwankaramazow20:02:51

what's your verdict in relay in general?

iwankaramazow20:02:10

in relation to Om Next for example

hueyp20:02:26

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

hueyp20:02:55

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

hueyp20:02:29

I struggle with om next and I’m not sure if its because I keep wanting to apply Relay concepts or what

hueyp20:02:36

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))))

hueyp20:02:49

like I have a book component, and it just wants the id, but then there is a book header and book details

hueyp20:02:53

and they both have data requirements on book

hueyp20:02:08

but if you do that in om … you mess up the indexer by erasing meta data on the query

hueyp20:02:06

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

iwankaramazow20:02:18

Is the graphql -> your backend queries a smooth story with relay?

hueyp20:02:31

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

hueyp20:02:43

I mean, you still put in UI concerns

hueyp20:02:06

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

iwankaramazow20:02:03

k, thanks for input simple_smile

hueyp20:02:56

I do think I need to read up on Redux and see their story on callbacks and routing

hueyp20:02:18

as I think om.next has a similar concept … transact! is synchronous and void

hueyp20:02:25

which I think is how you model actions in redux?

hueyp20:02:52

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 😜

hueyp20:02:24

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

hueyp20:02:33

in charge, I mean source of truth

iwankaramazow21:02:34

React Router is in charge of routing with redux 😛

iwankaramazow21:02:54

there are simple bindings floating around which keep the url in sync with the store

hueyp21:02:18

yah, I think @jlongster has written about this? I have dutifully starred these, but haven’t read them yet 😜

jlongster21:02:39

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.

anmonteiro21:02:47

@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 []

anmonteiro21:02:40

@dnolen: perhaps don't merge it yet. This only covers the case of the root class, I just noticed

anmonteiro22:02:02

@dnolen: moved it from add-root! to build-index*, should be good to go