最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to display component using react-router <Prompt> to prevent or allow route change - Stack Overflo

programmeradmin3浏览0评论

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 the getConfirmation function to the <Confirm> ponent to call it with true to allow transition, and with false to prevent it.
    • The callback would be called with 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:

  • The confirm dialog seems to block the function at that point. So the callback 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, the callback 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 the getConfirmation function to the <Confirm> ponent to call it with true to allow transition, and with false to prevent it.
    • The callback would be called with 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:

  • The confirm dialog seems to block the function at that point. So the callback 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, the callback 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
Add a ment  | 

2 Answers 2

Reset to default 9

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> 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} 
/>

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论