I'm currently trying to find a way to display a custom component (like a Modal) to confirm route changes using the Prompt component.
The default behavior of the Promp component 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> component.
The router has a prop named getUserConfirmation, which you can use to customize the behavior of the <Prompt> component.
// 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:
confirm state to true, to display the <Confirm> componentcallback from the getConfirmation function to the <Confirm> component to call it with true to allow transition, and with false to prevent it.true or false in the default behavior as you can see above.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:
confirm dialog seems to block the function at that point. So the callback variable/parameter is still in scope. So everything works OK.confirm dialog, that function runs all the way. And when I click on the confirm button inside the <Confirm> component, the callback no longer exists.QUESTION
Does anybody know a way to achieve this behavior (preventing route changes using a custom component 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="/" component={Home} />
      <Route exact path="/comp1" component={Component1} />
    </Switch>
  );
}
function Home(props) {
  console.log("Rendering Home...");
  return (
    <React.Fragment>
      <div>This is Home</div>
      <ul>
        <li>
          <Link to="/comp1">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 component 1</div>
      <Link to="/">Home</Link>
    </React.Fragment>
  );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
                Inspired 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> component 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="/" component={Home} />
      <Route exact path="/comp1" component={Component1} />
    </Switch>
  );
}
function Home(props) {
  console.log("Rendering Home...");
  return (
    <React.Fragment>
      <div>This is Home</div>
      <ul>
        <li>
          <Link to="/comp1">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 component 1</div>
      <Link to="/">Home</Link>
    </React.Fragment>
  );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With