navi icon indicating copy to clipboard operation
navi copied to clipboard

How do I prevent leave route?

Open mongris opened this issue 6 years ago • 18 comments

Thanks for the nimble navigation project. I am considering use navi instead of react-router in next project.

I want to redirect new user with initial password to change_password_page and prevent it leave the page before password changed. I can't find the impl in navi, can you help to figure out how make it works with navi?

mongris avatar May 29 '19 09:05 mongris

One possibility would be to put the user details in context, and then use a top-level map. Something like this:

mount({
  '/change-password': lazy(() => import('./change-password')),
  '*': map(request => {
    if (request.context.requiresPasswordChange) {
      return redirect('/change-password?redirectTo='+encodeURIComponent(request.url.href), { exact: false })
    }
    else {
      return routes
    }
  })
})

jamesknelson avatar Jun 06 '19 01:06 jamesknelson

@jamesknelson Thanks for your reply.

I need custom dialog to prompt user to change password first when he clicked other links to redirecting.

I dig into source code then I think maybe there should add some methods in Navigation class:

navigation.prototype.beforeLeave = (callback) => {
  this._beforeLeaveCheck = callback
  const allowed = this._beforeLeaveCheck()
  if (!allowed) this._history.block()
  else this._history.unblock()
  return unsubscriber(this._beforeLeaveCheck)
}

then use it like:

useEffect(() => {
  const check = () => {
    if (context.user.requiredPasswordChanged) {
      customDialog.show()
      return false
    }
    return true
  }
  return navigation.beforeLeave(check).unsubscribe
}, [])

mongris avatar Jun 10 '19 06:06 mongris

I need the same functionality. I need to prompt a user to save before navigating away. It needs to happen before the component unmounts.

roborich avatar Feb 06 '20 05:02 roborich

Nevermind. Redirecting back to the original component works. I had something in the component watching the route that caused it to re-render.

roborich avatar Feb 06 '20 22:02 roborich

Can we add a navigation.pause() method that sets the context within navi?

ColemanGariety avatar Apr 17 '20 20:04 ColemanGariety

@JacksonGariety that sounds like an interesting solution. Could you explain in a little more detail what navigation.pause() would be supposed to do?

jamesknelson avatar Apr 18 '20 01:04 jamesknelson

I didn't get how to not leave the route. I want to make any form to be able to cancel any route change request. So I put for example { unsavedData: true } into context and.. then what? How to cancel route transition? As far as I understand I cannot return null or false or undefined from map(). Nor can I retrieve the current route from within map().

  "*": map(async (request, context: RouterContext) => {
    // Check for unsaved data
    const unsaved = context.store.getState().app.unsavedData;
    if (unsaved) {
      // <<<<<<< How to prevent navigating to the requested route? How to CANCEL it? >>>>>>
    }
   ... 

OnkelTem avatar Aug 16 '20 16:08 OnkelTem

Nevermind. Redirecting back to the original component works. I had something in the component watching the route that caused it to re-render.

So you couldn't solve this task in the router, did you?

OnkelTem avatar Aug 16 '20 16:08 OnkelTem

I tried saving current route in the state but it didn't help too.

Say my current route is /profiles/3/edit which is a profile editing form. When the form gets changed I set app.unsavedData flag in the state and store the current route in app.currentRoute:

  "*": map(async (request, context: RouterContext) => {
    // Check for unsaved data
    const unsaved = context.store.getState().app.unsavedData;
    if (unsaved) {
      const currentRoute = context.store.getState().app.currentRoute; // == /profiles/3/edit
      return redirect(currentRoute);
    }

Now if I click say /profiles/2 link, instead of following to the /profiles/3/edit according to the redirect() (i.e. staying where it was), it now returns 404 for the new route:

NotFoundError: URL not found: /profiles/2

/profiles/2 actually does exist but for some reason it doesn't go there or stay where it was on /profiles/3/edit.

OnkelTem avatar Aug 16 '20 17:08 OnkelTem

@jamesknelson Do you have any idea? Navi is so powerful and asynchronous and flexible, I believe there should be a way to cancel a mere transition.

OnkelTem avatar Aug 16 '20 17:08 OnkelTem

@jamesknelson ?

OnkelTem avatar Aug 21 '20 09:08 OnkelTem

ping @jamesknelson

OnkelTem avatar Sep 02 '20 08:09 OnkelTem

Dear James.

Would you please have a look at this thread and share your vision? In my humble opinion, this is very important part of the routing to be able to cancel transition.

OnkelTem avatar Sep 27 '20 19:09 OnkelTem

ping @jamesknelson

Menci avatar Nov 27 '20 18:11 Menci

Perhaps it should simply mimic the browser's beforeunload event. So navi should expose a method to set and unset a confirmation string, and then navi should display that confirmation string before navigating away from the route.

ColemanGariety avatar Dec 06 '20 20:12 ColemanGariety

No, not just beforeunload. If you click "back" or "forward" button in browser's navigation bar, NO EVENT could let you cancel the history pop.

On Mon, Dec 7, 2020 at 04:27 clmn [email protected] wrote:

Perhaps it should simply mimic the browser's beforeunload event. So navi should expose a method to set and unset a confirmation string, and then navi should display that confirmation string before navigating away from the route.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/frontarm/navi/issues/93#issuecomment-739558683, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACWRBA2AWSMERSO6LRAJTLTSTPSLDANCNFSM4HQK6EAQ .

Menci avatar Dec 07 '20 06:12 Menci

Hi, is there any update on this? It still is an issue.

Edit: One solution that I found working in a React app is to use the history.block function. You can access the history object using the useHistory() hook. Here's a little snippet:

const history = useHistory();

useEffect(() => {
    history.block(() => {
        if (condition) {
            return "Your prompt message";
        }
    });
}, [condition]);

lukaszromerowicz avatar Apr 06 '21 13:04 lukaszromerowicz

Thanks @lukaszromerowicz. I will try it later.

mongris avatar Apr 07 '21 05:04 mongris