This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-05-18
Channels
- # admin-announcements (7)
- # arachne (24)
- # beginners (40)
- # boot (24)
- # braid-chat (22)
- # cider (8)
- # cljsrn (35)
- # clojure (32)
- # clojure-austin (1)
- # clojure-belgium (52)
- # clojure-russia (16)
- # clojure-sanfrancisco (1)
- # clojure-taiwan (2)
- # clojure-uk (25)
- # clojurescript (112)
- # core-async (3)
- # cursive (18)
- # data-science (1)
- # datascript (7)
- # datomic (30)
- # devcards (2)
- # dirac (12)
- # emacs (4)
- # flambo (1)
- # funcool (5)
- # hoplon (146)
- # jobs (9)
- # jvm (5)
- # off-topic (4)
- # om (141)
- # onyx (22)
- # re-frame (89)
- # reagent (86)
- # ring-swagger (31)
- # rum (3)
- # spacemacs (1)
- # specter (10)
- # untangled (112)
- # yada (3)
unless i’m doing it wrong there are definitely some subtleties with routing and modals (using CSS animations) when you have optimistic mutations
i’ve got a working solution in my app using a combination of local state and will-recieve-props (to avoid re-rendering modals and not using putting tempids in the URL, but putting the actual :db/id in the URL when it gets there)
it doesn’t feel pure but i suppose routing and tempid replace is stateful by nature
i’ve got three more modals to make in my app then i’d like to add what i’ve done as a cookbook recipe
@currentoor: I have a router working https://github.com/IwanKaramazow/om-router, I'll plan to submit a PR to the cookbook with it.
@iwankaramazow: have you tried this with untangled? i thought untangled did not use the read function?
nice write up by the way, very clear
also, how do you handle tempid, say I create a dashboard on the dashboard index page /dashboards/index
, i want the app to immediately open this newly created dashboard and route to /dashboards/:id
but :id
will be a tempid initially (because of optimistic mutation) then it will be resolved to an actual :db/id
so i want the UX experience to take advantage of the optimistic mutation but i also don't want the route history to have a tempid in it
We occasionally see this error in our console when clicking around our untangled app - anybody have an idea of where I'd want to start looking for this? "Uncaught Error: No protocol method IWithMeta.-with-meta defined for type cljs.core/Keyword: :untangled.client.impl.om-plumbing/not-found"
@therabidbanana: yes, it’s a mark-and-sweep-missing keyword
when you request data from the server, we check to make sure that the server returned data for every requested item in the query. If the server didn’t return data for a given item, but the client has data at that key (say, another client deleted the data at an earlier point in time), then we want to remove it from the client
we differentiate a nil
value returned from the server with no data returned from the server with the not-found
keyword
so if you’re seeing an error about it, it’s probably because you’re trying to access data in your client db that no longer exists on the server
So most likely culprit would be maybe a query on the client that accesses a property we don't send back?
that’s definitely possible, yup
I’ve seen it before when I query for something after resolving a tempid
because the request to the server has a tempid in it, but the tempid has since been resolved
Okay - that gives me some avenues to check. Thanks! Is this maybe a case where a better error message would be warranted from the untangled client? I could open an issue for it
Possibly, I’m not sure exactly how that would work… maybe modifying the read function to detect finding the keyword to throw a more a more helpful error. Can’t hurt opening an issue, fair warning we’re on crunch time here right now so probably won’t get to it for awhile
I agree though, a more helpful error is definitely called for
Oh, yep, definitely looks like we're trying to lazy-load data by a tempid. That probably explains it.
@currentoor: Untangled definitely lets you use reads, otherwise there won't be any data flowing down you React tree 😉 I haven't thought about tempids and routing. Hmm, I'll tinker with it for a bit
have you come up with any ideas ?
So my error traces back to untangled.client.data-fetch/load-field
being called with something that has a tempid - is that something that seems like an edge-case the load-field function should be able to handle? I can do a type check on our db/id to make sure it's not a om.tempid, but seems like something that might warrant a more generic solution?
So, just to be a devil's advocate...and I know this could start a heated debate: have you considered that when building a "desktop-like" experience, routing might not even make sense. Can you "go back" in Excel? IntelliJ? Emacs? etc etc etc. It adds a hell of a lot of complexity to the code base, which can hurt you in a lot of ways, and really isn't something users "can't live without". When is the last time you used the back button or a bookmark in gmail?
Hmm, that's some solid food for thought 😄
but I often find us bending over backwards to implement stuff that really isn't used
I know you fond of union queries for tab ui's
do you use them a lot at work?
yeah, unions are the only way we tab interfaces...easy enough to join up with routing as well...just change a set of idents
I tried implementing routing with union queries, but I often faced a situation where there wasn't an ident available, so I settled on joins
I think it were queries with mostly plain keywords, no idents in the mix
joins is not so great because you have to query it all every time..so for a large UI can do a lot of extra work per frame
Yea indeed...
Also those joins disrupt the query structure a lot
I always assumed it was more of a theory vs practice thing about Om's queries
@tony.kay: i agree about not going overboard with routes but since my app is reporting software we need some links, for example a scheduled report goes out weekly. users will get a link to a particular dashboard once a week
routing in an app like gmail does sound pretty useless though
If we continue over-engineering, there'll probably be some kind of history we need to keep about those links
Not so pretty, but I don't see another way atm
@currentoor: Yep. We do exactly that sort of thing in one place...Not saying it is useless 🙂
ours is a link to take a survey..but really we do it as a query parameter instead of routing...just pull it in and integrate it with initial load
NOTE to channel: Just pushed a recipe in the cookbook for doing the large paginated list (loading pages on demand)
https://github.com/untangled-web/untangled-cookbook/tree/master/recipes/paginate-large-lists
@baris: @darrellesh @pithyless One of your requested recipes is done.
Websockets example with server push was also added by @mahinshaw
awesome!
The websockets support comes from untangled-websockets. A new add-on library. NOTE: Websockets has some distinct limitations. The most glaring is an on-wire message size limit.
@therabidbanana: we have the same problem, only when using load-field. We haven't found a solution yet but are working on it
https://github.com/ptaoussanis/sente/issues/117 may actually be solved. We are using http-kit underneath, but that could easily be changed to fix that
@therabidbanana: So, the built-in remapping of tempids should affect all of app state, including the network send queue. Are you holding onto the tempid and doing some kind of later submit? We could (should) add an error message to load-field to help with this.
because obviously it doesn't make sense to try to load a field on something the server doesn't even know about yet.
We're calling load-field within the newly created dashboard widget's componentDidMount, if the data is not there already. As far as I know we're not holding onto it in some way, so the send queue should get rewritten from the sounds of it
why avoid them?
Where's a better place for it then?
It definitely did seem potentially hacky
We did end up embedding some crappy state checking logic. 😄
the transaction should have the mutation, and a read. You can use the app/load
mutation in your transaction. Not as API-nice as load-field, but not hacky
Twist here - we also don't read these with initial app state (large reports we only want if you open a specific dashboard)
So in that case we'd also want to transact the untangled/load in the transition to that page?
you can also trigger loads from another mutation. See load-data-action
and load-field-action
.
Cool - I'll take a look
@iwankaramazow: So, for all the reasons listed above...but for more clarity: loads happen for two real reasons in my view: initial app load, and user-triggered interaction. The former is covered by started callback in client, the latter can be handled via the options I just listed.
Out-of-band interactions (like server push) can also be handled without using lifecycle...see the websockets recipe
Also keeps your UI as a pure function...lifecycle messes with that, and is generally only desirable when integrating with stateful libraries like d3
load-field-action
definitely seems like something I was looking for - ended up building an (untangled/load... )
transaction with a focused query by hand, to happen after another mutation. This will definitely be helpful - thanks for pointing it out.
@tony.kay: looks like it wants me to override remote - which results in my original mutation not triggering if I try to use load-field-action inside a mutation I want to hit the server
(om/transact! this [(widget/new-data-stream ~%)])
My thought on that is to just have two mutations, since I want new-data-stream to reach the server
(om/transact! this [(widget/new-data-stream ~%) (widget/load-data-stream ~%)])
My understanding of how it works makes me think that's the right way to do it, but just wanted to confirm.
Now that I've asked my question, I guess I like the second approach anyway because it makes it easy to get rid of that lifecycle method and reuse the "load" transaction instead.
@kenbier thanks, looking at the code again that should be a very simple fix.
@therabidbanana: Sure, any number of mutations can occur (in sequence) and the order is preserved
The networking layer of untangled ensures one-at-a-time semantics unless you use the background option of load-*
So I guess the follow up to that - now that I have a transaction I want to reuse - how crazy do you think it would be to call om/transact! inside another mutation? (We have route hooks already set up to be transactions - so ideally our "dashboard/show" mutation could cue up a few follow on lazy load transactions if it needed them)
(Functionally it seems to work, can't decide if it's just a different kind of hacky to call transact! in a mutation vs the componentWillMount approach I'm replacing)
calling transact! inside of a mutation could get kinda screwy in terms of the render lifecycle
because transact manages what components are rerendered
you could manually call the mutation but… that’s kind of ugly
if you have conditional logic (or non-DRY stuff), feel free to compose real functions together to make the mutation
transacting against the reconciler causes full query/re-render, and could also lead to nasty things like mutual recursion that could blow your stack in a way that is hard to trace (if using from within mutations like that)
also remember that transact will trigger both a local AND remote (for each remote) parse.
BTW, it is ok for your :action
to call load-data-action
multiple times. Each call adds a load to the queue.
Hmm... I'll have to rethink our routing setup a bit then.