Preventing Transitions

Sometimes you want to prevent the user from navigating away from a page. The most common use case is when they have entered some data into a form but haven't submitted it yet, and you don't want them to lose it.

The <Prompt> component is gone, and its replacement, useBlocker(), only works underneath a data router (createBrowserRouter + <RouterProvider>) – it does not work inside a plain <BrowserRouter><Routes> tree. useBlocker() takes a boolean (or a function returning one) and gives back a "blocker" object you build your own confirmation UI around, with a "state" ("unblocked" / "blocked" / "proceeding"), and proceed()/reset() methods.


import React, { useState } from "react";
import { createBrowserRouter, Link, Outlet, useBlocker } from "react-router";
import { RouterProvider } from "react-router/dom";

function Layout() {
  return (
    <div>
      <ul>
        <li><Link to="/">Form</Link></li>
        <li><Link to="/one">One</Link></li>
        <li><Link to="/two">Two</Link></li>
      </ul>
      <Outlet />
    </div>
  );
}

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { index: true, element: <BlockingForm /> },
      { path: "one", element: <h3>One</h3> },
      { path: "two", element: <h3>Two</h3> },
    ],
  },
]);

export default function PreventingTransitionsExample() {
  return <RouterProvider router={router} />;
}

function BlockingForm() {
  let [isBlocking, setIsBlocking] = useState(false);
  let blocker = useBlocker(isBlocking);

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        event.target.reset();
        setIsBlocking(false);
      }}
    >
      <p>
        Blocking?{" "}
        {isBlocking ? "Yes, click a link or the back button" : "Nope"}
      </p>
      <p>
        <input
          size="50"
          placeholder="type something to block transitions"
          onChange={(event) => { setIsBlocking(event.target.value.length > 0); }}
        />
      </p>
      <p><button>Submit to stop blocking</button></p>

      {blocker.state === "blocked" && (
        <p style={{ color: "red" }}>
          Are you sure you want to leave?{" "}
          <button type="button" onClick={() => blocker.proceed()}>Yes, leave</button>{" "}
          <button type="button" onClick={() => blocker.reset()}>No, stay</button>
        </p>
      )}
    </form>
  );
}

If you just want a plain window.confirm() dialog instead of building custom UI, there's also unstable_usePrompt({ when, message }), a thin convenience wrapper around useBlocker(). It's intentionally kept prefixed "unstable_" permanently – the browser's back/forward buttons can behave inconsistently across browsers while a confirm() dialog is open – and it has the same data-router requirement as useBlocker() itself.