-
-
Notifications
You must be signed in to change notification settings - Fork 10.4k
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
V4 Feature Request: Blocking navigation with custom render instead of browser alert/confirm #4635
Comments
You are looking for |
Well that is not really what I am looking for. I could hardcoded that it always will be denied but not sure if that really is something that makes sense. I would rather have a component that could render a react component within the same scope as user confirmation. |
Within your |
But how would I render a react component in that method? And how could I change the react component for different Page components? That method is on the global routing object. |
@fkrauthan your question isn't related to react router, your question is really "how do i show a react modal when an event happens?" and the answer to that is going to depend on how you've structured the rest of your app. |
@phpnode No my question is very much related to react router: How can I prevent react router from switching routes and instead get a callback in my current component where I embed the navigation block. I think the current system and so far proposed addition are not really the react way and are very inflexibel as they mainly relay on outside react libraries for displaying modals and/or browser alert/confirm dialogs. |
I am also running into the same limitations with getUserConfirmation. My use case My current solution in v3 My concern in v4 This is a dealbreaker for me upgrading right now unless I can find a solution that behaves in a similar fashion. Right now in v3 it feels like a very clean way to handle this use case. Any solutions would be appreciated. EDIT |
@tacticalcoding |
Well again the Prompt Component does show you an alert or what ever you return as a dialog for getting user confirmation. It does not allow you to display a custom React component instead as soon as someone clicks on a link. |
I think I'm getting closer, and Prompt is really helpful for deciding when to show the message which I missed before. But is there a way render a custom component in getUserConfirmation? I've tried a few variations of this but am not really sure that I can get it to work.
This does work to show it, but I'm not sure how to unmount the component to close it. I'm currently experimenting with some other methods. Whats the proper way to render a component like you mentioned in a previous comment in the getUserConfirmation function? Thank you for your help. I do appreciate the thoughtfulness that goes into this library. |
@tacticalcoding Here is a very rough working implementation of |
@pshrmn But this has the disadvantage that you do not have access to your current route information as well as access to e.g. redux. A solution like this would cause a lot overhead and would not allow to change the type of Popup per use-case/page. |
@pshrmn Thank you that is helpful to me. Is there any worry about calling render in a callback like that overwriting the previous value? I see this comment taken from the docs but it is a bit vague:
|
@tacticalcoding subsequent render calls will update the existing component. If you are really concerned, you could architect this in a way that you unmount the component after the user clicks the button. Something along these lines (haven't actually tried it myself). const getUserConfirmation = (message, callback) => {
const holder = document.getElementById('modal')
const confirmAndUnmount = (answer) => {
ReactDOM.unmountComponentAtNode(holder)
callback(answer)
}
ReactDOM.render((
<Popup message={message} confirm={confirmAndUnmount} />
), holder)
} @fkrauthan If a |
But would there not be an option to create a react component that renders a children when it receives a transition. Passes in a callback that as soon as the child component calls it either accepts or deny the transition. I know it might be harder to code in the history project but that would be the ultimate solution to make everyone happy (e.g. adding a advances transition prevention API) or just adding some extra callbacks that I could register for that would call my component if a transition would happen so I could set the block transition and get a callback to confirm/deny it. |
I think that you've lost me now. Are we on the same page that the modal needs to be rendered separately from the rest of the application? |
Yes and that is exactly what I don't like right now (that's why I created this ticket). I want to have the page prevention rendering embedded in my current react application, Creating a second "application" is not really an option (your solution is to create a new react application within |
I view a custom You are obviously free to make a PR, but you would really need to champion it with examples of why your changes are necessary and why similar outcomes cannot be done with the current system. Right now, I just don't see there being a push to change how transition blocking works. |
Posted a working example here: https://gist.github.com/robertgonzales/e54699212da497740845712f3648d98c To connect your application's state you can partially apply whatever props you want to A custom |
Again I see big issues if you for example want a prompt do you wanna save first. And then when clicking yes I wanna be able to save the current form fields (e.g. API call) and then allow navigating away. use cases like that to me seem very common in web application and it feels like that having to render my Modal as a separate react application would make my live extreme hard. Or would your suggestion be for a use-case like that? |
Not saying there can't be improvement on the current API, but it's not hard to configure: configureUserConfirmation(whateverYouWantToPass) {
return function getUserConfirmation(message, callback) {
// setup render (see https://gist.github.com/robertgonzales/e54699212da497740845712f3648d98c#file-getuserconfirmation-jsx)
render(
<UserConfirmation {...whateverYouWantToPass} />
)
}
}
// pass props, state, a callback to save your form, etc.
<Router getUserConfirmation={configureUserConfirmation(whateverYouWantToPass)}>
{...}
</Router> |
So I can have multiple Router components in my tree without losing to much performance? |
That doesn't sound like a good idea. You should be able to use just the one. Ofc your form will need to be connected to a top-level store. |
Well but if I have two forms I would have to pass in different props^^ So I would need two routers. That's why I am saying the workaround is ok. But not very flexible. And just covers a small subset of actual real life features we might need for a web application. |
Right, your form would need to set a config object in a top level store. That config could be different depending on the form. You don't need two routers, I promise. :) |
Well but that would force me to manage UI Flows on two different locations. One time at the form itself. The second time I duplicate a lot logic for my alert dialog. Kinda makes the whole react approach useless if you ask me. |
Can we open this issue again? I am having the same problem as @fkrauthan. As far as I can see, there hasn't been any viable solution proposed here. As @fkrauthan has mentioned above, the "getUserConfirmation" method is far too limited and has too much of an overhead as it is treated as a separate react app. It only accepts a message argument and is isolated from the rest of the app's context. It would be great if the <Prompt when={this.props.unsaved} component={Confirmation}/>
// and
<Prompt when={this.props.unsaved} render={() => <Confirmation/>}/> |
+1 I'm struggling with the same problem. Always worked with RR v3 and used setRouteLeaveHook, but at the moment, we are using RR v4 and there is no clear example how to accomplish this. |
To show a confirmation box, only the `<Prompt>` component has to be added with the desired message. However, by default, the native confirmation dialog of the browser is displayed which is not that pretty. To display a nicer toastr confirmation box, we have to use the `getUserConfirmation` option when we create the history. At the moment, there is an ongoing discussion about how to use a custom component for the dialog here: remix-run/react-router#4635
I agree that using v4 for these sorts of things has proven very difficult. Is there a way for child components to define the handler for getUserConfirmation? And not using JSX. It would be nice if withRouter gave you access to setting getUserConfirmation. That would be ideal from my perspective. |
I have achieved something along these lines using this technique. In a component that is decorated withRouter:
I can then pass all the needed information on to my modal component which can allow the user to make choices concerning how the application will navigate. |
@jeremythuff how do you prevent the modal from opening that is triggered by calling |
+1 to jeremythuffs solution. Just wanted to leave a note on how and why it works as it was unclear to me at first. As he says you can drop this in any component that has withRouter or is a route component, so you can get access to The function passed to history.block is often shown returning a string in examples However you can just return a boolean to say So when he returns The next part of it is that So then, as in his code you can set the state to indicate that the modal should be displayed, and also take note of the location that the user was attempting to go to. So the rest of the code would look something like this:
|
Just as an FYI, here's a very relevant discussion about this exact feature, albeit not in regards to react router 4 specifically: remix-run/history#14 |
Well that discussion seems to be shutdown again with the remark that the library does not care for advance users and rather just do very simple use-cases that look good on hello world projects? |
It would be nice to have first class support for something similar to @jeremythuff's solution built into react-router. I think the basic concept could just be a Of course, it's also possible to implement such a component in user land, which is probably what we'll end up doing in our app. The existing solution offered by react-router is nice for use cases where the navigation prompts are not context aware and only the message varies, but it's not so nice when the prompts need to be context aware and vary more than just the message. |
I stumbled across this issue, because I also found The solution provided by @jeremythuff is really awesome. I put it into a reusable component, using the FaCC pattern (could probably be implemented as HOC as well). Here it is: https://gist.github.com/bummzack/a586533607ece482475e0c211790dd50 |
@bummzack Nice job! It would be great to turn this into a package (since React Router maintainers seem unwilling to add it to core). |
If you want to submit a PR for another package that implements it, that's exactly why we adopted the monorepo format. We're more than happy to integrate multiple projects into this one repo. Please give us your PRs! |
@timdorr that's interesting. :) I didn't know that. |
Just wanted to chime in to say that after coming to this issue and first thinking this was going to be a pain to upgrade from react-router 3 to 4 (showing a custom modal before leave) it turned out to be a one line code change for us, via the answer from @jeremythuff (here): Our |
UPDATE: react-router-navigation-prompt now confirms when navigating away from site, dependent on browser implementation. (UPDATE 2: fixed bug where history.block()'s callback accessed stale props) I adapted @bummzack 's solution to create this npm module: react-router-navigation-prompt. It shows its children Simplest example:<NavigationPrompt when={this.state.shouldConfirmNavigation}>
{({onConfirm, onCancel}) => (
<ConfirmNavigationModal when={true} onCancel={onCancel} onConfirm={onConfirm}/>
)}
</NavigationPrompt> Complex example:<NavigationPrompt
beforeConfirm={this.cleanup}
// Children will be rendered even if props.when is falsey and isActive is false:
renderIfNotActive={true}
// Confirm navigation if going to a path that does not start with current path:
when={(crntLocation, nextLocation) => !nextLocation.pathname.startsWith(crntLocation.pathname)}
>
{({isActive, onCancel, onConfirm}) => {
if (isActive) {
return (
<Modal show={true}>
<div>
<p>Do you really want to leave?</p>
<button onClick={onCancel}>Cancel</button>
<button onClick={onConfirm}>Ok</button>
</div>
</Modal>
);
}
return (
<div>This is probably an anti-pattern but ya know...</div>
);
}}
</NavigationPrompt> |
I looked thru the V4 website and examples and saw that you guys created a
<Prompt>
component. As I understand this will trigger a default Browser alert dialog. Is there anyway for certain areas to Overwrite that? My use-case would be for example to display a Bootstrap modal window instead of an unstyled Browser prompt. Or is that already possible? If yes a nice and easy to use API component (similar to the Prompt). Maybe something like<Locked>
or<Blocked>
where you can pass in the component you wanna display instead (or maybe just block without displaying anything?) would be amazing.The text was updated successfully, but these errors were encountered: