I'm currently trying to find a way to display a custom ponent (like a Modal) to confirm route changes using the Prompt
ponent.
The default behavior of the Promp
ponent is to show a confirm dialog with a message, as you can see in this Example: React Router: Preventing Transitions.
Note: I am using the <BrowserRouter>
ponent.
The router has a prop
named getUserConfirmation
, which you can use to customize the behavior of the <Prompt>
ponent.
// this is the default behavior
function getConfirmation(message, callback) {
const allowTransition = window.confirm(message);
callback(allowTransition);
}
<BrowserRouter getUserConfirmation={getConfirmation} />;
What I'm trying to do:
- Inside the parent ponent APP
- I'm setting the
confirm
state to true, to display the<Confirm>
ponent - And I'm trying to pass the
callback
from thegetConfirmation
function to the<Confirm>
ponent to call it withtrue
to allow transition, and withfalse
to prevent it. - The callback would be called with
true or false
in the default behavior as you can see above.
- I'm setting the
function getConfirmation(message, callback) {
console.log("Inside getConfirmation function...");
setConfirmCallback(callback);
setConfirm(true);
// const allowTransition = window.confirm(message);
// callback(allowTransition);
}
This is what App.js is rendering:
return (
<Router getUserConfirmation={getConfirmation}>
<AllRoutes />
{confirm && (
<Confirm confirmCallback={confirmCallback} setConfirm={setConfirm} />
)}
</Router>
);
What seems to be the problem:
- The
confirm
dialog seems to block the function at that point. So thecallback
variable/parameter is still in scope. So everything works OK. - When I remove the
confirm
dialog, that function runs all the way. And when I click on the confirm button inside the<Confirm>
ponent, thecallback
no longer exists.
QUESTION
Does anybody know a way to achieve this behavior (preventing route changes using a custom ponent instead of a confirm dialog) using react-router-dom
?
Link to CodeSandbox
Full code from CodeSandbox:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Route,
Switch,
Link,
Prompt
} from "react-router-dom";
import "./styles.css";
function App() {
console.log("Rendering App...");
const [confirm, setConfirm] = useState(false);
const [confirmCallback, setConfirmCallback] = useState(null);
function getConfirmation(message, callback) {
console.log("Inside getConfirmation function...");
setConfirmCallback(callback);
setConfirm(true);
// const allowTransition = window.confirm(message);
// callback(allowTransition);
}
return (
<Router getUserConfirmation={getConfirmation}>
<AllRoutes />
{confirm && (
<Confirm confirmCallback={confirmCallback} setConfirm={setConfirm} />
)}
</Router>
);
}
function Confirm(props) {
function allowTransition() {
props.setConfirm(false);
props.confirmCallback(true);
}
function blockTransition() {
props.setConfirm(false);
props.confirmCallback(false);
}
return (
<React.Fragment>
<div>Are you sure?</div>
<button onClick={allowTransition}>Yes</button>
<button onClick={blockTransition}>No way</button>
</React.Fragment>
);
}
function AllRoutes(props) {
console.log("Rendering AllRoutes...");
return (
<Switch>
<Route exact path="/" ponent={Home} />
<Route exact path="/p1" ponent={Component1} />
</Switch>
);
}
function Home(props) {
console.log("Rendering Home...");
return (
<React.Fragment>
<div>This is Home</div>
<ul>
<li>
<Link to="/p1">Component1</Link>
</li>
</ul>
</React.Fragment>
);
}
function Component1(props) {
console.log("Rendering Component1...");
const [isBlocking, setIsBlocking] = useState(true);
return (
<React.Fragment>
<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>
<div>This is ponent 1</div>
<Link to="/">Home</Link>
</React.Fragment>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I'm currently trying to find a way to display a custom ponent (like a Modal) to confirm route changes using the Prompt
ponent.
The default behavior of the Promp
ponent is to show a confirm dialog with a message, as you can see in this Example: React Router: Preventing Transitions.
Note: I am using the <BrowserRouter>
ponent.
The router has a prop
named getUserConfirmation
, which you can use to customize the behavior of the <Prompt>
ponent.
// this is the default behavior
function getConfirmation(message, callback) {
const allowTransition = window.confirm(message);
callback(allowTransition);
}
<BrowserRouter getUserConfirmation={getConfirmation} />;
What I'm trying to do:
- Inside the parent ponent APP
- I'm setting the
confirm
state to true, to display the<Confirm>
ponent - And I'm trying to pass the
callback
from thegetConfirmation
function to the<Confirm>
ponent to call it withtrue
to allow transition, and withfalse
to prevent it. - The callback would be called with
true or false
in the default behavior as you can see above.
- I'm setting the
function getConfirmation(message, callback) {
console.log("Inside getConfirmation function...");
setConfirmCallback(callback);
setConfirm(true);
// const allowTransition = window.confirm(message);
// callback(allowTransition);
}
This is what App.js is rendering:
return (
<Router getUserConfirmation={getConfirmation}>
<AllRoutes />
{confirm && (
<Confirm confirmCallback={confirmCallback} setConfirm={setConfirm} />
)}
</Router>
);
What seems to be the problem:
- The
confirm
dialog seems to block the function at that point. So thecallback
variable/parameter is still in scope. So everything works OK. - When I remove the
confirm
dialog, that function runs all the way. And when I click on the confirm button inside the<Confirm>
ponent, thecallback
no longer exists.
QUESTION
Does anybody know a way to achieve this behavior (preventing route changes using a custom ponent instead of a confirm dialog) using react-router-dom
?
Link to CodeSandbox
Full code from CodeSandbox:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Route,
Switch,
Link,
Prompt
} from "react-router-dom";
import "./styles.css";
function App() {
console.log("Rendering App...");
const [confirm, setConfirm] = useState(false);
const [confirmCallback, setConfirmCallback] = useState(null);
function getConfirmation(message, callback) {
console.log("Inside getConfirmation function...");
setConfirmCallback(callback);
setConfirm(true);
// const allowTransition = window.confirm(message);
// callback(allowTransition);
}
return (
<Router getUserConfirmation={getConfirmation}>
<AllRoutes />
{confirm && (
<Confirm confirmCallback={confirmCallback} setConfirm={setConfirm} />
)}
</Router>
);
}
function Confirm(props) {
function allowTransition() {
props.setConfirm(false);
props.confirmCallback(true);
}
function blockTransition() {
props.setConfirm(false);
props.confirmCallback(false);
}
return (
<React.Fragment>
<div>Are you sure?</div>
<button onClick={allowTransition}>Yes</button>
<button onClick={blockTransition}>No way</button>
</React.Fragment>
);
}
function AllRoutes(props) {
console.log("Rendering AllRoutes...");
return (
<Switch>
<Route exact path="/" ponent={Home} />
<Route exact path="/p1" ponent={Component1} />
</Switch>
);
}
function Home(props) {
console.log("Rendering Home...");
return (
<React.Fragment>
<div>This is Home</div>
<ul>
<li>
<Link to="/p1">Component1</Link>
</li>
</ul>
</React.Fragment>
);
}
function Component1(props) {
console.log("Rendering Component1...");
const [isBlocking, setIsBlocking] = useState(true);
return (
<React.Fragment>
<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>
<div>This is ponent 1</div>
<Link to="/">Home</Link>
</React.Fragment>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Share
Improve this question
asked Jun 19, 2019 at 9:39
cbdevelopercbdeveloper
31.5k44 gold badges198 silver badges394 bronze badges
3
- 2 This post might help you: ReactRouter v4 Prompt - override default alert – Shubham Khatri Commented Jun 19, 2019 at 9:53
- That did help! Thank you! – cbdeveloper Commented Jun 19, 2019 at 10:08
- Glad it helped. Consider upvoting such posts – Shubham Khatri Commented Jun 19, 2019 at 10:09
2 Answers
Reset to default 9Inspired by this discussion and by this example, I was able to make my example working.
The problem was that when the <Confirm>
was being created, the setConfirmCallback()
call wasn't done yet. So the <Confirm>
ponent wasn't able to use the callback
from getUserConfirmation
.
So I've changed this line:
FROM:
setConfirmCallback(callback);
TO:
setConfirmCallback(()=>callback);
And now it works!
CodeSandbox Link
Full CodeSandbox code:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Route,
Switch,
Link,
Prompt
} from "react-router-dom";
import "./styles.css";
function App() {
console.log("Rendering App...");
const [confirm, setConfirm] = useState(false);
const [confirmCallback, setConfirmCallback] = useState(null);
function getConfirmation(message, callback) {
console.log("Inside getConfirmation function...");
setConfirmCallback(() => callback);
setConfirm(true);
// const allowTransition = window.confirm(message);
// callback(allowTransition);
}
return (
<Router getUserConfirmation={getConfirmation}>
<AllRoutes />
{confirm && (
<Confirm confirmCallback={confirmCallback} setConfirm={setConfirm} />
)}
</Router>
);
}
function Confirm(props) {
console.log("Rendering Confirm...");
function allowTransition() {
props.setConfirm(false);
props.confirmCallback(true);
}
function blockTransition() {
props.setConfirm(false);
props.confirmCallback(false);
}
return (
<React.Fragment>
<div>Are you sure?</div>
<button onClick={allowTransition}>Yes</button>
<button onClick={blockTransition}>No way</button>
</React.Fragment>
);
}
function AllRoutes(props) {
console.log("Rendering AllRoutes...");
return (
<Switch>
<Route exact path="/" ponent={Home} />
<Route exact path="/p1" ponent={Component1} />
</Switch>
);
}
function Home(props) {
console.log("Rendering Home...");
return (
<React.Fragment>
<div>This is Home</div>
<ul>
<li>
<Link to="/p1">Component1</Link>
</li>
</ul>
</React.Fragment>
);
}
function Component1(props) {
console.log("Rendering Component1...");
const [isBlocking, setIsBlocking] = useState(true);
return (
<React.Fragment>
<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>
<div>This is ponent 1</div>
<Link to="/">Home</Link>
</React.Fragment>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I found a simple workaround for my case. I could not share the whole ponent but snippets.
// this will initiate the dialogbox render and
// prevent the window from going back by returning false
const backButtonPressed = async () => {
leavePrompt(false);
return false;
}
// this will open the prompt dialog box
const leavePrompt = (endRoom) => {
setOpenPrompt({open: true, action: endRoom ? "endRoom" : "leaveQuitely"});
}
// render
<Dialog open={openPrompt.open} aria-labelledby="interim-user-dialog-title">
<DialogContent dividers>
<Typography variant="h6" gutterBottom>
Are you sure?
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenPrompt({...openPrompt, open: false})} color="primary">
Stay
</Button>
<Button onClick={() => history.push("/")} color="secondary">
Leave
</Button>
</DialogActions>
</Dialog>
// when allowedToGoBack state is true then call a method that will render the dialog box
<Prompt
when={true}
title={"Alert"}
message={() => allowedToGoBack ? backButtonPressed() && false : true}
/>