This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-06-27
Channels
- # admin-announcements (3)
- # beginners (26)
- # boot (12)
- # cider (1)
- # cljs-dev (13)
- # cljsjs (101)
- # cljsrn (5)
- # clojure (64)
- # clojure-android (1)
- # clojure-gamedev (1)
- # clojure-greece (23)
- # clojure-nl (9)
- # clojure-poland (2)
- # clojure-russia (3)
- # clojure-spec (11)
- # clojure-uk (159)
- # clojurescript (19)
- # component (1)
- # core-async (2)
- # cursive (2)
- # datascript (1)
- # datomic (2)
- # devcards (1)
- # events (1)
- # funcool (1)
- # hispano (1)
- # hoplon (24)
- # immutant (12)
- # jobs (1)
- # keechma (18)
- # lein-figwheel (2)
- # leiningen (2)
- # off-topic (8)
- # om (23)
- # onyx (4)
- # planck (26)
- # re-frame (149)
- # reagent (6)
- # ring-swagger (9)
- # spacemacs (1)
- # specter (33)
- # spirituality-ethics (11)
- # testing (10)
- # untangled (335)
- # utah-clojurians (3)
- # vim (3)
- # yada (46)
should I be able to render "top-level" queries in nested components? For example, I have a LoadingIndicator
component 3 levels deep that I want to respond to :ui/loading-data
. I get/respond to it just fine in the Root component, and it looks like I have :ui/loading-data
in the queries at all 3 levels, but it's not finding it at all in the 2nd and 3rd-level components.
@curtosis: I haven't had a chance to use them yet. But if they are all at the top level of your app state (no idents involved) then you should be able to access them as 'links'. The advantage of links is that they can be accessed from any component, no matter how deeply nested it is.
Here's a short article: https://github.com/omcljs/om/wiki/Thinking-With-Links!
thanks! I'll take a look. clearly I've got something muddled, so hopefully getting a dose of theory will help.
or at least, I can make the top-level query in Root work, but none of the nested ones.
Here's a gist, if anyone is willing to take a look: https://gist.github.com/curtosis/3148230f9317455652c29f130db30b5a
hello, I might have found a bug on Untangled, I'm trying to do a remote query that has an union query, and it's failing when trying to normalize
after some inspection I think that some of the query metadata is being lost at some point
the error I'm getting is Uncaught Error: Union components must implement Ident
this is my query
[{:route/data
[{[:lesson/by-slug "meet-the-metronome"]
{:lesson.type/video [:lesson/type :lesson/description :youtube/id],
:lesson.type/playlist [:lesson/type :lesson/description],
:lesson.type/exercise [:lesson/type :lesson/title]}}]}]
query result:
{:route/data
{[:lesson/by-slug "meet-the-metronome"]
{:db/table :lesson,
:db/id 100,
:lesson/type :lesson.type/video,
:lesson/description
"You've probably seen a metronome before. There are old ones with a pendulum that swings from side to side and new digital ones that beep. But all of these devices are designed to help you establish a regular and steady beat. \r\n\r\nSometimes musicians find it useful to practice their playing along with a metronome to make sure that they aren't rushing ahead or lagging behind the beat. \r\n\r\nAfter lots of practice musicians tend to develop their own internal sense of time and beat which keeps their playing in time. ",
:youtube/id "ZYLKuoCeBK0"}}}
I put some logs in Om, when it tries to normalize, the component meta is not present in the query data, that's what makes me think the data is being lost on the way
and just confirming, my parent element has an ident for the union
@cjmurphy: still doesn't trigger. I'm also concerned that the :ui/app-version
query in TopBar also doesn't work.
You want to join to other components, not bring the keys in which you seem to want to do.
so when I do this for TopBar it still doesn't trigger for :ui/app-version
: (query [this] ['[:ui/app-version _] {:indicator (om/get-query LoadingIndicator)}])
and this on Root throws an invalid join error: (query [this]
`[:ui/react-key :ui/loading-data :ui/app-version {:top-bar (om/get-query TopBar)}])`
For the first thing, is :ui/app-version
in your app state when you check it in the Figwheel REPL?
my app-state from figwheel: {:ui/react-key "ROOT", :ui/app-version "2016.04", :ui/locale "en-US", :ui/loading-data true}
right now my TopBar query is (query [this] ['[:ui/app-version _] {:indicator (om/get-query LoadingIndicator)}])
So the :indicator
join ought to feature in the root query, just as you have it there.
I have (query [this]
[:ui/react-key :ui/loading-data :ui/app-version ~@(om/get-query TopBar)])` for Root.
I honestly don't know if links need to be in the root query. I have them in in my code, but as links.
well, I'm using :ui/loading-data
in the root query as well. (that one works, unless I write it as a link.)
but that's probably because my escape quoting is a little wonky and it ends up as [:ui/loading-data client.ui/_]
(top-bar)
- it is not being given any props. You need to have the join keyword and be feeding that to top-bar
.
d'oh. okay, I think it's making sense slowly. I got in a weird rabbit hole thinking the queries did the magic... which they kind of do, but still need to pass the info down into the components in render
, right?
Really you are passing down another list of hashmaps. You could just debug what is coming into root (pprint props)
, to see what is coming in.
well, I fixed all those joins and component calls. still no :ui/app-version
or loading data.
The :ui/app-version
is a separate problem. You could do a gist with just that in it.
Its usually set by Untangled right? And I can't see that you are setting it anywhere.
so... (om/get-query Root)
looks right... [:ui/react-key :ui/loading-data :ui/app-version {:topbar [[:ui/app-version _] {:indicator [[:ui/loading-data _]]}]}]
they're there in the query methods; I think they're just getting dropped by the pretty-print
revised: the props in Root don't have the subsubprops: {:ui/react-key "foo" :ui/loading-data true, :topbar {:ui/app-version "2016.04"}}
(no {:topbar {:indicator ...}}
)
When you go to the next components it brings out more data. I'm not 100% sure about this. But I don't think it is a problem that you don't get to see the data all the way to the bottom./
Not sure - but it would be a problem if you were not seeing the prop data in the lower level component where you need to see it.
LoadingIndicator does seem like a pointless component. All it has in it is a link. Seems odd to be joining to a component that does not have leaf keys.
the idea was that it's a reusable block that I can put anywhere, and it just trips state depending on :ui/loading-data
so in the data tree it's (conceptually) at the top level, but in the component tree it could be anywhere, even multiple places
It doesn't need to be passed prop data, or be joined to. It doesn't have to be in a tree.
@wilkerlucio: Did you figure out your problem?
@tony.kay: yes, I found a bug into Om ast->query
, it doesn't add the component meta back on unions, I'm going to issue a PR with a fix
I'm only using initial-state to stuff :ui/react-key
and :ui/app-version
into app-state for me.
Fair enough - if you can see app state as it should be then I guess there's no issue there.
but I agree... links don't seem like they should need props passed down. sort of the point.
@curtosis: I'm jumping in without reading everything...but links are not magic...they don't put things in props in arbitrary places
so things have to join together, and you have to pick the bits apart and pass them through
I've seen cases where if you have no app state path to a component, then you won't get the state of a component (having only a link query) because db->tree
will stop looking deeper when it runs out of state
This is an Om-ism. Not certain if it should be considered a bug. There is an easy workaround: make sure the path to the component exists in the graph of the db
(if it helps, the gist we're talking to is here: https://gist.github.com/curtosis/395a2406ed07c81304aeefcb77eaf333)
Yeah, in Root, you've composed in top bar, but it has no initial state. Give it initial state that composes in state from TopBar, which in turn should have initial state that has :indicator, which in turn composes in an empty map
the jumping-off point is more or less how to make a component that only depends on top-level (link) query data.
right, at the moment, the way queries work, it has to compose to root and have matching state, even if the real data you want to access comes from root via a link
the db->tree
algorithm stops running the query if it cannot follow a join in the graph db
You could also make TopBar and LoadingIndicator stateless (no queries) and pass the data down to them.
That is why I'm not sure it should be considered a "bug" per se. They don't really have data of "their own"...but then the query makes logical sense. Something to discuss on Om channel
I've been bitten by the same thing, of course. which is why the sol'n was "obvious" to me 😉
you still have to pick apart the state and pass it through in the body of the render
(TopBar was setting {:topbar ...}
in its initial-state, so I got {:topbar {:topbar {:indicator ...}}}
I second that - the video tutorials are fantastic. And I hate the video-ification of documentation.
@curtosis: wow, I'm flattered. (I'm not much of a video fan myself, but I know others like them...so I'm doing my best to make them useful)
And the working out where things went wrong is good too. Things like I never would have thought to inspect what's going across the wire using dev console. Can only get that sort of thing from videos.
just tried making LoadingIndicator stateless, fwiw. Works fine (I put '[:ui/loading-data _]
in TopBar's query, then just passed its value in a straight map. LoadingIndicator now has no initial state or query of its own.)
pro: LoadingIndicator is simpler; parent doesn't need to compose in initial-state and query.
con: its parent needs to know that it needs :ui/loading-data
, rather than just composing in the query and initial-state.
all of which is pretty obvious, but still worth calling out explicitly. there's a reason the composability of component queries is helpful. 😛
the deeper reason for trying to sort all this out, of course, is that NavBar (which I've elided here) needs to drive the "tab" switching and reflect the switched-in tab state, so it's all related.
does untangled eschew component-local state? thinking here of things like click-to-expand submenus.
Its eschewed I guess but makes sense sometimes - like a transact for every single keypress seems to much to me. You can do it if you want to of course.
(now I have to go back to the om docs and try to remember how to do local state... 😉 )
I only do component local state for form entry of text field...and then not always then. Support viewer is much more usable if you can see the user navigating the app
I've toyed with making it possible to elide transactions from history with some kind of metadata marker.
some kind of marker like ":unimportant" and then any adjacent unimportant history entry simply overwrites it. Automatically compressing adjacent unimportant entries down to one. That way you could do everything the same way, and choose what to elide from recorded history
I also want to start hanging network results (and errors) onto history entries so they can be shown in support viewer
Also need to compress (with something like data differ) the history so that support requests with history are reasonable on bandwidth.
I've just pushed an update to the Untangled tutorial: Section F-Untangled-Initial-App-State covers the new initial app state, including union support.
Online version has a couple of fixes, too. 3 SVG files were missing in the old version
BTW: online tutorial is using advanced optimizations on closure with Om and D3. Does a nice job of making a relatively small SPA: 1.4MB
Yeah, and that includes 200k of raw text content that cannot be "compressed" through renaming or elision
any tips for debugging tab switching? I'm seeing the new value show up in app-state under :tabs
but the Switcher itself isn't re-rendering the new panel
Only tip I have is to A-B compare with the getting-started video code (tabs-union tag)
make sure you didn't make the same mistake as I did: naming idents using keywords that are also top-level keys
e.g. :tabs as your top-level state key, but also as an ident key...causes the table to overwrite the state
The good news is the pattern is well established in code. It is a little tricky to understand at first, but the only real mistakes you can easily make have to do with naming things. Make sure your ident uses the keyword first (and that the keyword in the ident matches the table name of the table holding the thing you're trying to point to)...that keyword is also what you switch the UI on
so far doesn't look like a name collision... if I put in some dom/button
s at the Root level to transact the tab switch, it works fine. so it must be something in how I'm calling the transact from the nav menu? maybe?
it almost looks like calling transact!
from anywhere outside root isn't triggering Switcher to re-render.
the only thing I can think of is maybe something in the way I'm setting initial-state to include my work from earlier today.
If you're trying to get a sibling to refresh on a transact, you'll have to use follow-on reads. If you want a parent to refresh,you should use transact in the parent, and pass a callback of that into the child for triggering
there is much value in "oh, I recognize that problem." and again, very much additional value in ".. and here's a video addressing specifically what causes that problem."
the one sort of unintuitive bit is that I have it do a follow-on read of :tabs
, which is what the thunk changes... but Switcher doesn't query for :tabs
just a proxy decision maker...it uses the queries of the children, and the renderers of the children
I suppose :tabs
works to trigger the refresh because in root it gets the Switcher's query, which in turns gets the tabs' queries, which themselves get :type
, which is what Switcher renders based on. Do I have that chain reasonably correct?
It isn't quite that complicated. When you say :tabs
as a follow-on read: Om looks up every component that mentions :tabs
in a query, and re-renders them all.
When your UI goes on the screen, Om indexes every on-screen component by the keywords in its query
the nice thing is you don't have to care which components need refresh...you just need to name the bits of the graph that are affected
We all want to know "how it works", so it is nice to know the index bits and such...but the real power is that it frees you from having to think about the UI structure. Everything you do (mutation and refresh) is based on reasoning in the graph
right. In this case, we're changing :tabs
so we probably do want to re-render all the children; it wouldn't make sense necessarily to just flag :type
as having changed (or so it seems to me)
right, type is something you needed to query, but it isn't the part of the graph that changed
re my comment/question about "what makes Switcher 'not real'" => I realized it was a question for me b/c my Switcher is a real component; it renders some html divs around the tab. There's no magic that makes a Switcher "not real" other than not doing its own rendering/queries.
there's some tricky stuff to wrap your head around initially, but I think I'm finally getting to the point where I'm seeing the clarity of reasoning it enables.
So if I wanted to use xhr requests for most of my remote mutations and reads but also supplement them with some server side pushes using sente, any recommendations?
It looks like the untantled-websockets library makes me pick xhr XOR websockets?
the websockets library handles both remote mutation, reads, and push when you use it. Since it reduces the total number of connections needed, I'd use it. @mahinshaw wrote that library, so perhaps he can comment.
@mahinshaw: reading your code now, looks really nice!
there are no xhr requests. You could put that on a side band, but there is no need. Mutations and reads, as well as push all go through the same plumbinbg
i figured we might want to reads to go through xhr so we can still leverage http caching, at the browser level
the nice thing is the websockets are another endpoint, so you can still hit the api
endpoint that you are used to
To get that going in the same app, I think you'd have to have multiple remote support, which is not there yet. Unless you're using XHR directly
the problem though is reads and mutates will still go through the websocket by default since we override the networking, but you can do ajax
what if i write my own networking component that wraps both the websockets one and the regular /api
one? then my component just decides who to pass the request off to?
is that feasible?
So, it would not be a ton of work to support multiple remotes, and have each use it's own networking stack. That would give you both. Mutations would then say :my-remote true
instead of just :remote true
....each remote would have it's own key
browser limitations on connections could get a little dicey if you, say, wanted multiple websocket remotes...but whatever
like I was saying...the main issue is the network queue...it is currently a singleton
right, the queue lives in the implementation of UntangledNetwork/send
, and websockets has a different implementation
but that shouldn’t be too bad, since we are using core async. we just need a router.
but if I'm only going to use websockets for pushes, do you think it's a bad idea to avoid a networking component altogether, just have a separate component that opens a websockets, listens for pushes (mutation literals from the server), and transacts them into the untantled-app
does that sound like an unwise thing to do?
It is only unwise in that browses limit the total number of connections (browser-wide) to a domain
so, if you expect ppl to open more than one tab you're possibly asking for trouble sooner
i see, thanks. well two connections seems acceptable IMO. it would be nice to get a google docs like functionality in our app
since most of the things in our app are dashboards shared between users
I keep seeing this in my logs:
[1574.490s] [om.next] transacted '[(app/choose-tab {:tab :settings-tab}) {:tabs {:dashboard-tab [:id :type :extra], :settings-tab [:id :type :args]}}], #uuid "e80e791a-fa49-4aa9-aeb3-15ac20a653d3"
you say :tabs
, Om looks up the component in the index, gets it's query, and adds that to the transaction as the actual read to run
oh, duh. I was just seeing that :dashboard-tab
was always there and didn't notice that it was the full query. operator error .... a good sign I've been at the keyboard too long today. 😛
any suggestions for what composes with untangled-server to get user authorization, and limit what mutations are allowed
FYI, those using old-style app state (not new app state). There was a bug in new code that breaks code with unions that are NOT using the new app state mechanism. Fixed in 0.5.4-SNAPSHOT, which I just pushed
@jasonjckn: yeah, sorry, can't answer that