React-router: How I can wait asynchronously when leaving route?

Created on 28 Apr 2016  路  16Comments  路  Source: ReactTraining/react-router

That's disallowed by design for compatibility with "before unload" listeners.

Let's proceed here. So how I can do something like this (what worked perfectly with rr 0.13.x) in new router:

static willTransitionFrom = async (transition, component, callback) => {
        await saveCurrentData();
        callback();
    }; 

I understand that for beforeunload you can't implement asynchronous callback so may be it worth to split those hooks?

All 16 comments

You can use the onLeave hook, which is async.

Actually I don't see in docs that this function is async :(

Actually, you're right. But does it matter? You're not going to be able to show the result of that async action, since your components are going away.

Yes, it matters. I want to wait for response and according to response prevent leaving or not.

Unfortunately, I don't think we're going to support this. We need to be sync for onbeforeunload support. You'll have to replace <Link> with your own and call any leave functions from there.

But as I said before you can split onLeave into 2 callbacks ... Anyway that is very sad that new react-router breaks old good behavior

You'll have to replace with your own and call any leave functions from there.

What about browser navigation, hotkeys (backbutton, swipe on IOS)?

breaks old good behavior

I wouldn't call it good behavior.

Your old transitionFrom hooks only ran when you were staying in your app. If you clicked back or forward to example.com then your transition hooks wouldn't even get called.

We now can support running these hooks when navigating inside your app as well as outside of it with window.onbeforeunload, which is synchronous.

You have a running app, and you can always know what you need to know synchronously because your app is already running. So, if you think you need an async hook, you actually just need to track some application state instead.

Thank you for support. Actually I'm implementing optimistic updates on that page and when leaving it I want to be sure that every change has been saved. If you are curious I did it in the next way:

componentDidMount () {
        let router = this.context.router;
        let unlistenLeave = router.setRouteLeaveHook(this.props.route, nextLocation => {
            this.onLeave().then(() => {
                unlistenLeave();
                router.push(nextLocation);
            }).catch(() => {
                unlistenLeave();
                router.push(nextLocation);
            });

            return false;
        });

We now can support running these hooks when navigating inside your app as well as outside of it with window.onbeforeunload, which is synchronous.

It seems that the hook setRouteLeaveHook does not listen onbeforeunload event. All I was able to find is https://github.com/mjackson/history/blob/master/docs/ConfirmingNavigation.md . And here you have to attach beforeUnload callback to history.

@taion Do you mind responding to https://github.com/reactjs/react-router/issues/3404#issuecomment-216899488 here?

@deser You have to add useBeforeUnload for it to listen to those events.

Specifically, this line means you have to make leave hooks synchronous. You cannot logically back out of a leave event asynchronously. If you hit the back button in your browser (which triggers this), the beforeunload event can only be cancelled (return 'some text') within that same execution context. There is no callback in the spec, so you can't hook back into that context once you leave it (which is what happens when you do something asynchronous).

You can fire an async action from your leave hook, but you can't block any navigation from it. The best you can do is fire another navigation event (push/replace) after that async action resolves. But you should treat it like componentWillUnmount, in that you can not use or rely on anything in your component because it is going away.

BTW, you can do custom async behavior with a custom user notification handler, though if you need to send through any data context, you'll need to round-trip through a store or something (and bind the confirmation getter to that store).

The core API is set up this way to prevent things from just breaking in the "before unload" case, as mentioned multiple times above.

The best you can do is fire another navigation event (push/replace) after that async action resolves.

That's exactly what I've done :)

Still guys I see no problem to pass into onLeave additional argument (callback) when transition could be stopped and undefined instead of this argument when transition couldn't be stopped.
Anyway I found the solution which satisfies me. Thanks

Hi @deser

Do you mind telling us what is your solution please ?

Hey, it's mentioned above:

componentDidMount () {
        let router = this.context.router;
        let unlistenLeave = router.setRouteLeaveHook(this.props.route, nextLocation => {
            this.onLeave().then(() => {
                unlistenLeave();
                router.push(nextLocation);
            }).catch(() => {
                unlistenLeave();
                router.push(nextLocation);
            });

            return false;
        });

But be aware that sometimes crazy things with router occur when using this solution. For example it can cycle 2 routes between each other. Or put into history one route twice so user has to click back twice.

Was this page helpful?
0 / 5 - 0 ratings