I am having an issue with navigation blocking in React.
I use React + Redux with React Router. I have been using the component in order to prevent navigation from incomplete forms within React. This works fine as long as the navigation is actually within React. What it doesn't block, however, is navigation via URL entry, or clicking on an external hyperlink.
Is there an accepted method in React for handling external hyperlinks and blocking navigation outside of React?
You didn't provide any code here, so I'm trying to guess. If I understand you correctly, you are able to manage you internal redirects thought the React app via react-router without any issues.
As per your statement:
What it doesn't block, however, is navigation via URL entry, or clicking on an external hyperlink.
Let's tackle both questions. First can't prevent a user from going to the navigation bar and enter a new URL, that done by design on the browsers side, it would be bad for the user, and of course invasive!
But regarding your second question about clicking on a link that will send you outside your domain or application, you can do something about it.
You have multiple options here, I will give you three:
First: Use history.block
You can block or better said, ask the user to acknowledge the transition to another page by using history.block
As an example from the history docs:
const unblock = history.block('Are you sure you want to leave this page?')
Second: Use history.push instead of href
Just don't use anchor elements href, but rely on the history of react-router.
You can read more about it here: react-router/history
But basically you can wire your redirection using the push method from history, which will look something like:
onClick() {
//code to control if you want to redirect or not
history.push('http://yoururl.com');
}
Third: Use Redirect component with conditional rendering
Then you have other options like for example using conditional rendering combined with Redirect components, but the other approach would be enough to solve your problem.
I think you are looking for Blocking Transitions under the history api for React Router.
As per the example on react router history github page:
You can register a simple prompt message that will be shown to the user before they navigate away from the current page.
const unblock = history.block('Are you sure you want to leave this page?')
Detailed info at https://github.com/ReactTraining/history#properties
Related
I have created one spa using JQuery. SPA is done via writing a custom router where we add visible classname to a page div to make that page visible and we remove the same classname from all the other pages using jquery like this,
$(".main-content .page").removeClass("visible");
const page = $(`#${pageId}`);
page.addClass("visible");
Now I am migrating this site to use react components here and there through out the website. I have one component on one of the page that shows product data based on the id in the URL but the problem is that it doesn't rerender even if I change the URL. It is not able to detect the change as state or props are not changing.
is there a way to force rerender this component from my other JS code?
The solution was pretty straight forward but it is not mentioned anywhere.
The official guide says to use this below code at the end of the file so that when our js file will be loaded by the browser and run then it will render our react code but the thing is we can call this method from anywhere to force rerender our component/ replace our component with the new instance.
To force rerender it, calling below method that adds the react code to the div worked.
ReactDOM.render(<ProductDetails />, document.getElementById("product-details"));
Using this we can even pass the new props also if we want to.
React Router Link component takes in target="_blank" to open the page in a new tab. Is there a way to do the same with the Redirect component?
The way my components are set up, I can't wrap Link around the component in question (there is a button on the component, clicking on which also redirects, which I don't want) that's why I'm using Redirect instead, but can't find a way to open the new URL in a new tab, How can it be done?
Is there a way to do the same with the Redirect component?
Short answer:
No.
Bit longer:
Technically <Link> creates an <a> tag for you which works with target="_blank". From the docs:
Provides declarative, accessible navigation around your application.
<Redirect> is used for something else, as the documentation states:
Rendering a <Redirect> will navigate to a new location. The new location will override the current location in the history stack, like server-side redirects (HTTP 3xx) do.
So in summary <Redirect> is updating the history, <Link> is used as an anchor tag. At the end of the day if you really need to use target="_blank" then I would suggest to use <Link> or create an <a> tag for that purpose.
Earlier I have create a GitHub repository which represents redirect options, please find it below:
https://github.com/norbitrial/react-router-programmatically-redirect-examples
I hope that helps!
In react-router v3, I've been using router.setRouteLeaveHook to check if a form has unsaved changes, and if so return false to prevent the transition. Then I would display a custom bootstrap modal dialog with 3 buttons: Save Changes, Discard Changes, and Stay Here.
I can't use react-router v4's Prompt component to do this because it's not possible to customize the buttons shown in a browser confirm dialog. It seems like they got rid of any way to cancel the transition in favor of only allowing you to ask the user to approve the transition in a browser confirm dialog.
I tried looking in the code for Prompt but it just passes the dialog message to history, so that doesn't give me any idea how to set up a v3-style route leave hook.
Is it even possible anymore or did the react-router devs intentionally decide to remove this capability this for some reason?
According to the history package docs, you can replace window.confirm with anything you like:
By default, window.confirm is used to show prompt messages to the user. If you need to override this behavior (or if you're using createMemoryHistory, which doesn't assume a DOM environment), provide a getUserConfirmation function when you create your history object.
So if you want to use your own dialog, something like this should see you through:
const history = createHistory({
getUserConfirmation(message, callback) {
showMyCustomDialog(message)
.then(result => callback(result === 'The YES button'))
}
})
This means that whatever getUserConfirmation message you set is set for the entire session, but you could abstract it out to your navigation blocking layer that holds additional details for your dialog, e.g. title, button text, colours etc.
Alternatively, you could hijack the message argument and use it for dialog configuration, though that may smell a bit nasty. But it's not a perfect system so anything you do will probably be a bit of a hack.
React Router v4 allows you to pass this method through when you create your router (see here):
<BrowserRouter getUserConfirmation={yourConfirmationFunction} />
Can use Prompt to show custom dialogue. Credit and detailed explanation here.
Prompt requires a message prop, here we can use a custom function for a dialogue and it should return false to prevent navigation.
const BlockingPage = () => {
const [block, setBlock] = useState(true);
const blockedNavigation = (nLocation) => {
//nLocation gives the next location object
/**
* Your custom logic
*
**/
//required to block navigation
return false
}
return(
<div>
<Prompt when={block} message={blockedNavigation}/>
</div>
)
}
I don't think this is possible. react-router-v4 uses a package called history that in turns uses the HTML5 history api. The history api only notifies you when you hit the back button (onpopstate), If you think about it this makes lots of sense, since you would not want to give a website the power of not letting you move between pages.
The best you can do is the window onbeforeunload event, that creates a prompt for you asking confirmation from the user, but this is exactly what react-router exposes for you to use.
You might get some of the functionality you want by monkey-patching react-router's internal history object, and that way you can add your own behaviour. But there is a caveat, this is only going to work when you react-router's <Link /> component and friends, so you will not be able to intercept refreshes and other things you may want.
If you want to go that route, let me know I can give you some insight or code examples about how might that work and I will update my answer.
I have a react single page application using React Router, and having paths defined using Link as shown below:
<Link to="second" className="level-2" params={{id:item.content}} title={item.name}>{item.name}<br/></Link>
This changes url from /myapp/firsturi to /myapp/seconduri
Is it possible for me to make the same react router action using vanilla JS? I tried using history.pushState() and history.replaceState() but while the url updates correctly, the content on the page does not. I can get the same thing using window.location.href="/myapp/seconduri" but that causes a page refresh which is not what I want.
Any idea how to go about this?
React Router relies on the history module to handle locations. When the page loads it parses the initial URL, which is why setting window.location.href works.
The history that you use includes a listen function which React Router uses to listen for location changes. When one occurs, it will trigger a re-matching of the new location against your routes, which in turn will render the correct components.
Manually calling pushState/replaceState does not trigger the listening function, which is why your app is failing to update. Instead, you should use your history instance to navigate.
This has worked for me:
// simulate navigation to where you want to be (changes URL but doesn't navigate)
window.history.pushState("","","/url");
// simulate navigation again so that
window.history.pushState("","","/url");
// when you simulate back, the router tries to get BACK to "/url"
window.history.go(-1);
Everything else I tried was just forcing the DOM to reload again as if it was a link click.
i am trying to show the user a payment popup as soon as he clicks on a payed object.
But after he pays he should directly enter the content he clicked on.
Therefore i think its a good solution to solve this with the router, because i want every link on the page that redirects to this content to show this popup.
My problem is i want to show the popup before redirecting the user.
So i tryed the onBeforeAction hook and stuff but everything working with the iron router seems to only hook in after the URL of the browser changed and the current template was unloaded.
Do you have an idea how to get this kind of behavior?
Cheers
Based on this answer, here is how you can hook the router using Router.onStop():
// onStop hook is executed whenever we LEAVE a route
Router.onStop(function(){
//check if the current route is the page from where you need to show your
//popup and show it based on, for instance, a session variable containing
//the previously clicked content id.
});
It's a common use case that I don't think is directly achievable within the iron router framework at present (although I would be delighted to be corrected!). As you've discovered, onBeforeAction is run before the page has rendered but after the new route has been run, so the old page has already disappeared.
Effectively, you're looking to queue the running of a new route until a certain action has been completed. The use case for which I've experienced this requirement is page transitions, for which the best solution appears to be to do completely the opposite of what you propose: i.e. to add the logic to an event attached to the link, and only redirect to the new route once that logic has been satisfactorily completed (i.e. the popup has been closed in your case).
I agree that doing something in the router would be a sensible way to approach this, but I'm not sure it's possible in iron router as things stand. Note that this has already been raised though!
Will this workshop?
'unload - runs just once when you leave the route for a new route.'
From
https://github.com/EventedMind/iron-router/blob/devel/DOCS.md#unload-hook