solid-router icon indicating copy to clipboard operation
solid-router copied to clipboard

Some kind of `onbeforeunload` for routes?

Open mattmccray opened this issue 3 years ago • 2 comments

Perhaps I'm missing something, but is there a way to register a callback for route changes... And cancel them? Like an onbeforeunload but for routes?

function TestScreen(props) {
  const route = useRoute()

  route.beforeTransition(e => { // maybe 'e' contains target route info?
    // (user code to check for form changes...)
   return false // false to prevent transition?
  })

  return (
    <form> . . . </form>
  )
}

I don't know about the api specifically, that's just as an example of potential usage.

mattmccray avatar Jul 09 '21 21:07 mattmccray

Yeah, there currently isn't a way to do this. I agree it would be a worthwhile feature to add. Thinking about it brings up a lot of questions how exactly it should work and what the expectations are. Just thinking out loud here:

The handlers would have to operate at the router/location level but you would likely want to register them on a route close to your form or whatever (like you illustrated). So if you had multiple registered on say a parent route and a child route, and the location change affected both routes, would they both fire? Or maybe just the root-most one? If they did both fire then if either rejected the transition would be cancelled.

Cancellation of navigation initiated from the router would be easy enough to handle but locations changes from the integrated system like history.popState will have to be more like a revert so I'll have to figure out how to make that work correctly.

I'm going to think about this a bit and see how others like React-Router handle this.

rturnq avatar Jul 10 '21 05:07 rturnq

It's definitely tricky.

Just my two cents:

  • Route change handlers could be registered at the route level -- Either via the <MatchRoute> component or from the useRoute() hook.
  • Any handler, at any level, that returned falsy would cancel the navigation -- Instead of the old return false from an event handler approach, it could use an event.returnValue = "" or even event.preventDefault()... Maybe the latter is best?
  • Which is all fine and dandy with internal navigation, as you said, but trickier when the navigation is external -- back button or manually entered URL.
  • And I foresee added difficulty with hash navigation. With the History API, you can use replaceState to revert to its last value without adding to the browser's navigation history. But I don't think such a thing is possible with hash navigation.

Interestingly, React Router doesn't seem to support routing events, per se. They expose a Prompt component that when when={true} will show a browser prompt rather like the onbeforeunload one...

<Prompt when={isBlocking}
  message={location =>`Are you sure you want to go to ${location.pathname}`}
/>

Haven't dug into the internals there, and I'm not sure how I feel about that... Actually, I am sure. "Prompt" is a horribly named component. 😄 But using a component to control the state of whether a navigation should be allowed is intriguing.

Perhaps this is cleaner, come to think of it. You don't really have hooks to intercept route changes, but you do have a way to prevent navigation without notifying the user.

Granted, component or no component, the internals would need to support all the prevention and rolling back of the navigation state.

Alright, now I'm just rambling. Sorry. For the app I'm working on, I'm moving to an "all CRUD objects are mutated in modal dialogs" style of UI. It doesn't really address the back button issue, but I can easily disable/disallow internal navigation this way.

mattmccray avatar Jul 10 '21 16:07 mattmccray