-
-
Notifications
You must be signed in to change notification settings - Fork 10.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[V6] [Feature] Getting usePrompt
and useBlocker
back in the router
#8139
Comments
I tried to find a workaround with |
Is there any guidance on how to implement the same behavior? I didn't find any extension point within the current router implementation. |
Removing |
I also experienced this and just found out it happened very unexpected in beta7. +1 for bringing it back or at least outline some workaround. |
As react-router expose 256cad7#diff-b60f1a2d4276b2a605c05e19816634111de2e8a4186fe9dd7de8e344b65ed4d3L344-L381 import * as React from 'react'
import { UNSAFE_NavigationContext } from 'react-router-dom'
import type { History, Blocker, Transition } from 'history'
export function useBlocker(blocker: Blocker, when = true): void {
const navigator = React.useContext(UNSAFE_NavigationContext).navigator as History
React.useEffect(() => {
if (!when) return
const unblock = navigator.block((tx: Transition) => {
const autoUnblockingTx = {
...tx,
retry() {
// Automatically unblock the transition so it can play all the way
// through before retrying it. TODO: Figure out how to re-enable
// this block if the transition is cancelled for some reason.
unblock()
tx.retry()
},
}
blocker(autoUnblockingTx)
})
return unblock
}, [navigator, blocker, when])
} Don't exactly know why |
If you really need blocking I'd recommend you remain on v5 until we have time to get it back into v6. v5 will continue to be supported for the foreseeable future. You shouldn't feel any pressure to upgrade to v6 immediately. As for why it was removed in v6, we decided we'd rather ship with what we have than take even more time to nail down a feature that isn't fully baked. We will absolutely be working on adding this back in to v6 at some point in the near future, but not for our first stable release of 6.x. |
I might add (for those of you who want to upgrade immediately to v6) that you could consider another type of user experience instead of blocking navigation away from the current page. The canonical use case for blocking is so you can show the user a prompt to confirm navigation away from a page with unsaved data. Instead of blocking navigation, another valid way to handle this situation could be to just save the state of the form to local/session storage in the browser as the form values change. Then, when the user returns, repopulate the default values of all the form fields with the ones you saved from the last visit. This experience should be less jarring than throwing up a prompt in front of the user while still giving you the resilience you're looking for. Again, we are planning on re-adding this functionality to v6 after our stable release. I only offer this suggestion as a possible alternative for those of you who may wish to upgrade immediately. |
Yes, that's completely understandable. Looking forward for v6 release, great work 👏 My question was about this change, with omitting 'block' from Navigator |
@piecyk You can always create your own type to add that method back. I simply removed all the methods from the |
A version with breaking changes notice would've been nice prior to a major release. Right now, the project recommends to upgrade, yet the migration guide is partial and application critical features have been removed without notice. (After updating to v6, I am currently debugging a few errors with |
I also ran into this omission while trying to migrate from v5 to v6. My migration was going so well! I love the changes that come in v6. They are super intuitive. Really great job!!! Thank you! I will wait patiently for I will also attempt to convince my company's product owners to use this pattern when possible. Thanks for the suggestion! |
We need a way to cancel a long running process if the page is navigated away from in an electron app. There are more use cases than form data or unsaved changes. |
Thanks @piecyk ! I made a gist of this for react-router-dom for easier access: |
Nowhere is the removal of this mentioned anywhere in the v5 to v6 upgrade docs. I just burned an hour trying to figure out why I couldn't resolve Prompt until I stumbled upon this issue. |
A note has been added in #8436 |
|
The following (and probably some newer versions) is working with React Router v5:
|
@mjackson Is there a roadmap for when this will be prioritized and worked on? Got 97% of the way with my upgrade, and the remaining bits are comprised of |
This comment was marked as duplicate.
This comment was marked as duplicate.
are these features still not implemented? do we have a timeframe? |
There is a draft PR open at #9709. This is a work-in-progress but keep an eye on the activity there to see where things stand. |
Hi there. If anyone is also waiting for the usePrompt and useBlocker hooks to appear, in version 6.5.0 you can override the navigate function of the router this way: const router = createBrowserRouter(routesArray);
const _navigate = router.navigate.bind(router);
type Listener = () => boolean | Promise<boolean>;
const listeners: Listener[] = [];
router.navigate = async (...args) => {
const params = args as [any];
if (listeners.length > 0) {
const promises = listeners.map((fn) => fn());
const values = await Promise.all(promises);
const allowed = values.every(Boolean);
if (!allowed) return;
}
return _navigate(...params);
};
const beforeUnload = (e: BeforeUnloadEvent) => {
// Cancel the event.
e.preventDefault();
// Chrome (and legacy IE) requires returnValue to be set.
e.returnValue = '';
};
function blockNavigation(fn: Listener) {
if (listeners.length === 0) {
addEventListener('beforeunload', beforeUnload, { capture: true });
}
listeners.push(fn);
return () => {
const index = listeners.indexOf(fn);
listeners.splice(index, 1);
if (listeners.length === 0) {
removeEventListener('beforeunload', beforeUnload, { capture: true });
}
};
} You can then use the function useBlocker(dirty: boolean, blocker: Listener) {
useEffect(() => {
if (!dirty) return;
return blockNavigation(blocker);
}, [blocker, dirty]);
} And use it in the project (you should change the code to suit your needs, this is just an example): function usePrompt(message: ReactNode, dirty = true) {
useBlocker(
dirty,
useCallback(
// async function:
async () => await confirmUnsavedChanges(message),
// or just confirm
// () => confirm('Unsaved changes!'), // boolean
// or just boolean function:
// () => doWeNeedToGo(), // boolean
[message]
)
);
} |
This bug was likely introduced when upgrading to React Router 6. The `UpdateStatus` component needed access to the router's state in order to block navigation (and show a confirmation dialog to the user) when there are unsaved changes. With the upgrade to React Router 6, this required wrapping class components in a custom `withRouter` HOC. The `UpdateStatus` component exposes the different states (`SUCCESS`, `ERROR`, `IN_PROGRESS`) as static class fields (e.g. `UpdateStatus.IN_PROGRESS`). When wrapping the component in `withRouter`, those static class fields aren't forwarded, i.e. `UpdateStatus.IN_PROGRESS` is undefined. However, at the moment, blocking navigation isn't possible anyway (at least temporarily), as React Router v6 has removed support for relevant features required to implement this (although it will hopefully return soon [1]). So for now, I have simply removed `withRouter` altogether. Once blocking navigation is possible again, we should refactor this component to a function component and use the hooks provided by React Router. [1] remix-run/react-router#8139
This comment was marked as off-topic.
This comment was marked as off-topic.
This bug was likely introduced when upgrading to React Router 6. The `UpdateStatus` component needed access to the router's state in order to block navigation (and show a confirmation dialog to the user) when there are unsaved changes. With the upgrade to React Router 6, this required wrapping class components in a custom `withRouter` HOC. The `UpdateStatus` component exposes the different states (`SUCCESS`, `ERROR`, `IN_PROGRESS`) as static class fields (e.g. `UpdateStatus.IN_PROGRESS`). When wrapping the component in `withRouter`, those static class fields aren't forwarded, i.e. `UpdateStatus.IN_PROGRESS` is undefined. However, at the moment, blocking navigation isn't possible anyway (at least temporarily), as React Router v6 has removed support for relevant features required to implement this (although it will hopefully return soon [1]). So for now, I have simply removed `withRouter` altogether. Once blocking navigation is possible again, we should refactor this component to a function component and use the hooks provided by React Router. [1] remix-run/react-router#8139
Thanks for all the awesome progress on this! I really love the loaders feature in v6.4+ and wanted to be able to keep using that while being able to move forward with a migration that relies on I know that ends up being sort of ugly and hacky - but is working well for my current use case. I really appreciate the direction the current PR is going, but have two requests for the final version.
Thanks for all the hard work on resolving this important use-case in a deterministic and easy to maintain way - y'all rock! |
Unfortunately I think both of those suggestions would be leaking hacky implementation details and foot-guns. We will likely opt to do as we did before: give you the best effort attempt to block navigation without breaking the back button in most cases, but outline the tradeoffs. |
|
Does anybody know why I'm getting this erro while running tests when using I'm using In my test file I render the component like this:
|
Hey folks! |
I have the same issue as @vladutclp, with both |
@dennisoelkers Please open a new Q&A discussion, this is a closed issue. |
|
As a point of feedback, the documentation could be significantly improved for how to transition the previous setup using a createBrowserRouter(
createRoutesFromElements(
... but was ultimately unsuccessful and had to roll back. |
We are working on documentation as we speak, but yes it should (and will) definitely be improved. Locking the issue now that we've wrapped up the core work. Feel free to open new issues if you run into usage problems, and stay tuned for docs! |
I think in general most people won't be able to upgrade to v6 since in the latest beta
usePrompt
anduseBlocker
are removed.Most apps rely on the
usePrompt
orPrompt
to prevent navigation in case the user has unsaved changes (aka theform
is dirty).With this issue maybe we can have some feedback on why they (
usePrompt
,Prompt
) were removed (they worked fine in the previous beta version v6.0.0-beta.6) and what makes them problematic, what's the outlook for getting them back in the router and potentially userland solutions to this problem.The text was updated successfully, but these errors were encountered: