-
Notifications
You must be signed in to change notification settings - Fork 16
Conversation
Make me refactor my code to use PUSH instead of NAVIGATE if you must. But in my opinion "works like push 95% of the time, but instead works like FOCUS the other 5% of the time you already have a route by that name in the stack" is not an intuitive behaviour. |
@dantman - why refactor from navigate to push? why not add a key to navigate action which adds some identifying information for the screen?
I think of it a bit differently -- navigate to the route 100% of the time, and instantiate an instance of the route if it doesn't exist. Does this framing make more sense?
I'm not too concerned about this. The solution to the question is super easy: add a |
Fixing the double event submission fixes the problem in 100% of my use cases. Adding a key doesn't fix 100% of my use cases. And on top of it it adds unnecessary code to every single one of my navigation calls. An idempotent navigation doesn't fix my buttons tied to AJAX requests. And a |
what do you mean by this? debouncing the handler or what?
not sure I follow this either, can you elaborate? |
Not a timed debounce. When an async event handler starts lock it (reject further calls to the event handler) until the event handler has finished its work. ie: Any ajax requests have finished, any promises have finalized, and any navigation transitions have completed. My personal plan for handling this in the app(s) I work on is to write a decorator for this, and see if I can get the necessary "wait for any pending navigation transitions to complete" method in React Navigation to make another decorator. @lockEventHandlerWhileRunning // I'll come up with a better name later
@waitForNavigationTransitions // maybe shorten this name
async onSubmit() {
const {data, navigation} = this.props;
const res = await this.makeAnApiCall(data);
if ( res.ok ) {
navigation.goBack(); // exit full-screen dialog
} else {
this.showError();
}
}
Say you have a button/list item that creates a document (maybe based on a template) and then sends you to the document page that is used to view and edit that document. Here's some simplified mock code. onPressDocumentTemplateItem(templateId) {
const templateDocument = await getTemplateDocument(templateId)
const document = {
...templateDocument,
id: uuid(),
};
await createDraft(document);
navigate('Document', {key: `document-${document.id}`, id: document.id});
} If you skip the key and documents have links to other documents, then you replace the Document route and can't return to where you navigated from. If you use the document's ID to differentiate between documents, then you haven't solved the double submit issue because each press creates a different draft with a different id. |
interesting, I'd be curious to understand the use case for this. in the case you describe it would already be handled properly because the
would a user potentially have two drafts open at the same time? if not, then just use a 'draft' key, otherwise you'd have to add some handling to debounce the button press. I think this is an uncommon edge case. for example, if you go to compose a tweet in twitter you can only have one of those open in a stack at any given time. same for writing a post on your wall in fb, writing a draft in medium, editing a photo in photos, creating a post on instagram, composing an email in any mail app, etc. am I miss something that makes this similar to common use cases? |
Perhaps not the best example. These decorators are intended for all event handlers, whether they use goBack, navigate, or another action. So they work equally well in the avatar onPress in a comment section. Also, goBack being idempotent doesn't help fix the fact that when the submit button is double-submitted
Besides the draft no longer being a draft if it's saved, I dislike the idea of using different keys depending on which event handler opened the route. I'm working on an app that works pretty much like this; this is just how the API works, I have to create a draft on the server with an API call, and then I have to use the resulting ID to push the same route used to view one that's already been saved; I cannot open a route and then save drafts from it email style. And yet again, double submit of the API call. I suppose the most similar use case would be a text editor that works with a 3rd party API that limits what it is able to do. |
it's not clear to me that the problems you're describing are related to react-navigation ‐ they seem like more of a concern of either userspace logic around blocking interactions until an async action completes or react-native (if there is some bug preventing you from implementing that logic). for example, the pattern used in this snack would solve the problem for you: https://snack.expo.io/By9N3KAPM (although you probably would want to have some loading indicator or state to indicate that the button is blocked from further interaction). i can't see how we would handle this kind of case automatically with react-navigation. further, i'm not convinced that it has bearing on the viability of this rfc. i am of course very open to being shown incorrect! |
As someone fairly inexperienced with react-navigation I like this proposal. I do feel like the attraction for me is having to deal with keys less. That may be a good thing or not, perhaps it points out a flaw in my mental model around keys and how react-navigation works under the hood. I don’t know how routers or navigators do what they do and what role key plays in that. Is it just like having to set a key when rendering multiple React component in that it must be unique? And if I push a route with a key that already exists it will replace it with the new route? If I’m right then I only realised that after reading this proposal 😄 |
|
||
# Detailed design | ||
|
||
We need to change [this branch](https://github.com/react-navigation/react-navigation/blob/2bb91a6740e2f1ade012906dc03c972ba6375cc3/src/routers/StackRouter.js#L185-L190) of StackRouter. The logic will be similar to the [`if (action.key)`](https://github.com/react-navigation/react-navigation/blob/2bb91a6740e2f1ade012906dc03c972ba6375cc3/src/routers/StackRouter.js#L200) branch, but rather than simply match for key we'll match for `routeName` within `state.routes`. |
This comment was marked as abuse.
This comment was marked as abuse.
Sorry, something went wrong.
This comment was marked as abuse.
This comment was marked as abuse.
Sorry, something went wrong.
I'm definitely in support of this change. It is more in line with the intended idempotent behavior of navigate. Now that we have an explicit push action which always pushes, I think it makes sense to change the semantics of navigate in this way. Unfortunately, it is a significant breaking change. I think is is probably worthwhile to push forward with this, but the re-education efforts will definitely be more work than the change to the code. |
Also, (this is kind of a side note), for @dantman's use case of creating a draft before pushing the screen, I would recommend pushing immediately rather than waiting on a network request, to give the user a snappy experience. Also I would note that the double-tap behavior is not specifically a navigation problem. We probably want to use debounced buttons for many cases in our apps, especially when firing network requests. |
for a stacknavigator, if you use |
This is a great proposal. I would have expected it to work this way, so it feels more natural and would solve some issues for me. Overlaying some load animation or another distracting in-screen animation on willFocus should be easy in user space. |
So to accomplish some of the demoes that are out there to highlight infinite pushability of screens on the stack. Would they all need to generate a unique key to identify the screen otherwise they would all cease to function? |
navigate(...) reuse an existing screen if a screen already exists, comparing routename and if given the key That's how I understand the RFC: "If you want to be able to push an endless number of screens here, you would need to either 1) call |
Ah gotcha yeah, misread that. |
@browniefed - nope, it would just work on the current stack, although we could make it accept a key to push to another stack (pr welcome for this, should be pretty easy, look at what i did recently for pop to top). the examples that show pushing an unlimited number of screens are pretty contrived, if you wanted to re-create those examples you would use push, or you can use navigate with a key as in the code example in rfc. would love to hear about cases where this behavior is used in the real world and doesn't have any unique data associated with the screen -- i can't think of any. i can think of one weird situation though:
|
That is a slightly common situation in my app. Home page of interest. Either way I think I'm on board with the changes. Trying to wrap my head around what the transitions would look like with high level StackNavigators that are |
I don't care about idempotent NAVIGATE with keys, they just add work for me that is 100% unnecessary. And I have a bunch of StackNavigators because its the only way to make the navigation structure of our app in react-navigation work. I was happy to just refactor to use PUSH instead, presuming that it just worked the same as NAVIGATE used to minus the navigate back behaviour I never needed. But if you're telling me that PUSH isn't even a working replacement for the current NAVIGATE if you have different navigator levels, then I have a problem with this RFC. The use of keys in order to have multiple entries of the same route should not be made mandatory until there is a viable option for people to opt-out of that and just PUSH routes. |
@dantman - can you provide some code that demonstrates the problems you see? it's not clear to me |
@brentvatne I'm referring to your comment suggesting that PUSH doesn't work outside of the current StackNavigator like NAVIGATE does. |
@dantman - right, I follow that. I want to understand why that is a problem though. it's unclear to me what the use case is for having places all over your app where all of the following constraints apply:
I don't doubt that I could just be lacking in imagination here but I'm unable to map this to the flows in any app that I know of after some time thinking about it. I really want to be sure I'm not missing some important use case here |
I'm open to adding an |
It’s hard to evaluate this without concrete use cases. The FB profile use case that Brent mentioned does have a solution. The correct thing in my mind, which is also how the real FB app behaves, is to always push a new profile, even if it is already in the stack. The push action would be appropriate here, but you’d need to denounce your button to prevent the double navigation |
@ericvicenti - that makes sense. removing scoping for |
@brentvatne Meanwhile the 85% of my routes are for Profile-like things where I pass ids/data to them and would not want navigate to just focus an existing screen. And topping it off at some point in the future some of those may have the circular Profile<->Comments like navigation pattern where I would not want any of the buttons to accidentally pop off a bunch of routes just because it happened to link back to the same "Profile". So, fixing double submit at the event handler level for me is non-negotiable; idempotent NAVIGATE is not necessary. So with that out of the way, if PUSH worked like NAVIGATE without the focus, I can just refactor my code with a few find-and-replace operations to use PUSH instead of NAVIGATE because I don't need NAVIGATE. Otherwise if I have to keep NAVIGATE because PUSH sometimes doesn't work, I have to go to 85% of my navigate calls and instead of just doing a navigate->push I have to add a key to the navigate call, doing manual work for every one to identify the best ID to use for that call. And I don't have a use for those keys, this manual work and extra code in nearly every navigate call is not in service of making my app better (because I don't need it to fix double navigation issues), this manual work is just to undo the fact that react-navigation has changed so these 100% useless to me keys are now mandatory for my app to work like normal. But I have an unfortunately complex navigator hierarchy. It's actually worse than the example I'm about to give, right now the DrawerNavigator has separate routes for each of the different Inbox types and Clients/Firms/etc... where each of those has its own StackNavigator inside of them because that was how DrawerNavigator works for making drawer items. I'm in the process of refactoring it to look like the following example where I just put a single StackNavigator inside the Drawer and do all the drawer items myself and just RESET/NAVIGATE the Stack as necessary to reduce the necessary StackNavigators. Stack = StackNavigator[+drawer menu button handling] {
Inbox,
...
Firms,
Firm,
Tags,
Tag,
Transaction,
TransactionViewer,
}
Drawer = DrawerNavigator {
Stack: Stack,
/**
* The actual drawer items are something like:
* Workspace: Inbox{bundle=myworkspace}
* Upcoming: Inbox{bundle=upcoming}
* Snoozed: Inbox{bundle=snoozed}
* Drafts: Inbox{...draft search query params...}
* ...
* Firms: Firms{}
* Tags: Tags{}
*/
}
ModalNavigator = StackNavigator[mode=modal, headerMode=screen] {
NoModal: Drawer,
DialogFoo,
DialogBar,
...
DialogN,
}
Navigator = StackNavigator[header=null] {
Login,
Logout,
ForgotPassword,
Main: ModalNavigator,
}
And the top level So that's the answer to "why so many StackNavigators?". StackNavigator has a bunch of different modes that can only be set at the Navigator level so you need to create multiple to get some types of UI to work. And sometimes interaction with/encapsulation of other types of navigators necessitates another StackNavigator layer. (Also keep in mind that this app is incomplete, the web service this app accesses has piles more functionality that in the future I will likely be working on adding to the app) Right now If PUSH can't do the same thing, then I can't use it in half my app. And as I said the necessary keys to use the new NAVIGATE are completely worthless to me and just add more work and potential unexpected bugs down the road. Having to write To illustrate the "circular Profile<->Comments like navigation pattern". Imagine you have a Profile route with a comments area, and all the comments have an avatar that push on a Profile route. Imagine you visit Alice's profile, tap the avatar of one of Bob's comments, then tap on one of Joe's comment avatars. You're now on "Home > Profile{Alice} > Profile{Bob} > Profile{Joe}" (though in reality it could be much much deeper). Now imagine there is a reply from Alice and you tap on her avatar. Which do you think is the correct behaviour? Navigate back to "Profile{Alice}" so now the back button returns you to "Home" instead of back to the "Profile{Joe}" profile you were on. Or push a second "Profile{Alice}" on so you have a stack of "Home > Profile{Alice} > Profile{Bob} > Profile{Joe} > Profile{Alice}". I have a scenario like this with my |
thank you for the detailed explanation! it sounds like the case where you may end up wanting navigation to a new route with a key based on data doesn't make sense in this significant case we've both identified (which you describe in the "circular Profile<->Comments like navigation pattern"). I think the solution here is to have push behave like navigate does currently, rather than being scoped to the deepest navigator. I think this makes a lot more sense with the changes to |
No description provided.