> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/remix-run/react-router/llms.txt
> Use this file to discover all available pages before exploring further.

# useBlocker

# useBlocker

Allow the application to block navigations within the SPA and present the user a confirmation dialog to confirm the navigation. Mostly used to avoid losing half-filled form data.

<Note>
  This hook only works in Data and Framework modes.
</Note>

<Warning>
  This does not handle hard-reloads or cross-origin navigations. Use the browser's `beforeunload` event for those cases.
</Warning>

## Signature

```tsx theme={null}
function useBlocker(
  shouldBlock: boolean | BlockerFunction
): Blocker

type BlockerFunction = (args: {
  currentLocation: Location;
  nextLocation: Location;
  historyAction: "PUSH" | "REPLACE" | "POP";
}) => boolean

interface Blocker {
  state: "unblocked" | "blocked" | "proceeding";
  location?: Location;
  proceed(): void;
  reset(): void;
}
```

## Parameters

<ParamField path="shouldBlock" type="boolean | BlockerFunction" required>
  Either a boolean or a function that returns a boolean indicating whether the navigation should be blocked.

  When using the function format, it receives:

  * `currentLocation` - The current location
  * `nextLocation` - The location being navigated to
  * `historyAction` - The type of navigation (PUSH, REPLACE, or POP)
</ParamField>

## Returns

<ResponseField name="blocker" type="Blocker">
  An object with the following properties:

  <Expandable title="properties">
    <ResponseField name="state" type="'unblocked' | 'blocked' | 'proceeding'">
      The current blocker state:

      * `"unblocked"` - The blocker is idle and has not prevented any navigation
      * `"blocked"` - The blocker has prevented a navigation
      * `"proceeding"` - The blocker is proceeding through from a blocked navigation
    </ResponseField>

    <ResponseField name="location" type="Location">
      When in a `"blocked"` state, this represents the location to which navigation was blocked. When in a `"proceeding"` state, this is the location being navigated to after a `proceed()` call.
    </ResponseField>

    <ResponseField name="proceed" type="() => void">
      When in a `"blocked"` state, call this function to proceed to the blocked location.
    </ResponseField>

    <ResponseField name="reset" type="() => void">
      When in a `"blocked"` state, call this function to return the blocker to an `"unblocked"` state and leave the user at the current location.
    </ResponseField>
  </Expandable>
</ResponseField>

## Usage

### Block with boolean

```tsx theme={null}
import { useBlocker } from "react-router";
import { useState } from "react";

function Form() {
  const [value, setValue] = useState("");
  const blocker = useBlocker(value !== "");
  
  return (
    <>
      <form>
        <input
          value={value}
          onChange={(e) => setValue(e.target.value)}
        />
      </form>
      
      {blocker.state === "blocked" && (
        <div className="modal">
          <p>You have unsaved changes. Are you sure you want to leave?</p>
          <button onClick={blocker.proceed}>Leave</button>
          <button onClick={blocker.reset}>Stay</button>
        </div>
      )}
    </>
  );
}
```

### Block with function

```tsx theme={null}
import { useBlocker } from "react-router";
import { useState, useCallback } from "react";

function Form() {
  const [isDirty, setIsDirty] = useState(false);
  
  const shouldBlock = useCallback(
    ({ currentLocation, nextLocation }) => {
      // Only block if form is dirty and navigating away
      return (
        isDirty &&
        currentLocation.pathname !== nextLocation.pathname
      );
    },
    [isDirty]
  );
  
  const blocker = useBlocker(shouldBlock);
  
  return (
    <form
      onChange={() => setIsDirty(true)}
      onSubmit={() => setIsDirty(false)}
    >
      {/* form fields */}
      
      {blocker.state === "blocked" && (
        <ConfirmDialog blocker={blocker} />
      )}
    </form>
  );
}
```

### Proceed on form submit

```tsx theme={null}
function ImportantForm() {
  const [value, setValue] = useState("");
  const blocker = useBlocker(value !== "");
  
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        setValue("");
        
        // If blocked, proceed after saving
        if (blocker.state === "blocked") {
          blocker.proceed();
        }
      }}
    >
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <button type="submit">Save</button>
      
      {blocker.state === "blocked" && (
        <div className="dialog">
          <p>Blocked navigation to {blocker.location?.pathname}</p>
          <button onClick={blocker.proceed}>Leave without saving</button>
          <button onClick={blocker.reset}>Keep editing</button>
        </div>
      )}
    </form>
  );
}
```

### Custom confirmation dialog

```tsx theme={null}
function ConfirmDialog({ blocker }) {
  if (blocker.state !== "blocked") return null;
  
  return (
    <div className="modal-overlay">
      <div className="modal">
        <h2>Unsaved Changes</h2>
        <p>
          You have unsaved changes. Do you want to leave without saving?
        </p>
        
        <div className="actions">
          <button
            onClick={blocker.reset}
            className="primary"
          >
            Stay and Save
          </button>
          <button
            onClick={blocker.proceed}
            className="danger"
          >
            Leave without Saving
          </button>
        </div>
      </div>
    </div>
  );
}

function Form() {
  const [isDirty, setIsDirty] = useState(false);
  const blocker = useBlocker(isDirty);
  
  return (
    <>
      <form onChange={() => setIsDirty(true)}>
        {/* form fields */}
      </form>
      
      <ConfirmDialog blocker={blocker} />
    </>
  );
}
```

## Common Patterns

### Block only external navigation

```tsx theme={null}
function Form() {
  const [isDirty, setIsDirty] = useState(false);
  const location = useLocation();
  
  const shouldBlock = useCallback(
    ({ nextLocation }) => {
      // Only block if leaving the form section
      return (
        isDirty &&
        !nextLocation.pathname.startsWith("/forms")
      );
    },
    [isDirty]
  );
  
  const blocker = useBlocker(shouldBlock);
  
  return <form onChange={() => setIsDirty(true)}>...</form>;
}
```

### Show destination in dialog

```tsx theme={null}
function Form() {
  const [value, setValue] = useState("");
  const blocker = useBlocker(value !== "");
  
  return (
    <>
      <form>
        <input
          value={value}
          onChange={(e) => setValue(e.target.value)}
        />
      </form>
      
      {blocker.state === "blocked" && (
        <div className="modal">
          <p>
            You're trying to navigate to{" "}
            <strong>{blocker.location?.pathname}</strong>
          </p>
          <p>Unsaved changes will be lost.</p>
          <button onClick={blocker.proceed}>Continue</button>
          <button onClick={blocker.reset}>Cancel</button>
        </div>
      )}
    </>
  );
}
```

### Auto-save before proceeding

```tsx theme={null}
function AutoSaveForm() {
  const [value, setValue] = useState("");
  const [isSaving, setIsSaving] = useState(false);
  const blocker = useBlocker(value !== "");
  
  const saveAndProceed = async () => {
    setIsSaving(true);
    await saveData(value);
    setIsSaving(false);
    setValue("");
    blocker.proceed();
  };
  
  return (
    <>
      <form>
        <input
          value={value}
          onChange={(e) => setValue(e.target.value)}
        />
      </form>
      
      {blocker.state === "blocked" && (
        <div className="modal">
          <p>Save your changes before leaving?</p>
          <button onClick={saveAndProceed} disabled={isSaving}>
            {isSaving ? "Saving..." : "Save and Leave"}
          </button>
          <button onClick={blocker.proceed}>Leave without Saving</button>
          <button onClick={blocker.reset}>Stay</button>
        </div>
      )}
    </>
  );
}
```

### Multi-step wizard

```tsx theme={null}
function Wizard() {
  const [step, setStep] = useState(1);
  const [data, setData] = useState({});
  const isComplete = step === 3;
  
  const blocker = useBlocker(
    useCallback(
      ({ nextLocation }) => {
        // Block if wizard incomplete and leaving wizard
        return !isComplete && !nextLocation.pathname.startsWith("/wizard");
      },
      [isComplete]
    )
  );
  
  return (
    <div>
      <h1>Step {step} of 3</h1>
      {/* Step content */}
      
      <button onClick={() => setStep(step + 1)}>Next</button>
      
      {blocker.state === "blocked" && (
        <div className="modal">
          <p>You haven't completed the wizard. Progress will be lost.</p>
          <button onClick={blocker.proceed}>Leave</button>
          <button onClick={blocker.reset}>Continue Wizard</button>
        </div>
      )}
    </div>
  );
}
```

### Block during async operations

```tsx theme={null}
function Component() {
  const [isUploading, setIsUploading] = useState(false);
  const blocker = useBlocker(isUploading);
  
  const handleUpload = async (file: File) => {
    setIsUploading(true);
    await uploadFile(file);
    setIsUploading(false);
  };
  
  return (
    <>
      <input
        type="file"
        onChange={(e) => handleUpload(e.target.files[0])}
      />
      
      {blocker.state === "blocked" && (
        <div className="modal">
          <p>Upload in progress. Are you sure you want to cancel?</p>
          <button onClick={blocker.reset}>Wait for Upload</button>
          <button onClick={blocker.proceed}>Cancel Upload</button>
        </div>
      )}
    </>
  );
}
```

## Important Notes

### Browser navigation

`useBlocker` only blocks in-app navigation (using `<Link>`, `navigate()`, etc.). It does NOT block:

* Browser back/forward buttons (use `beforeunload` event)
* Page refreshes (use `beforeunload` event)
* Closing the tab/window (use `beforeunload` event)
* External links

For those cases, use the browser's `beforeunload` event:

```tsx theme={null}
useEffect(() => {
  const handleBeforeUnload = (e: BeforeUnloadEvent) => {
    if (isDirty) {
      e.preventDefault();
      e.returnValue = "";
    }
  };
  
  window.addEventListener("beforeunload", handleBeforeUnload);
  return () => window.removeEventListener("beforeunload", handleBeforeUnload);
}, [isDirty]);
```

### Stable function reference

When using a function for `shouldBlock`, use `useCallback` to ensure a stable reference:

```tsx theme={null}
const shouldBlock = useCallback(
  ({ currentLocation, nextLocation }) => {
    return isDirty && currentLocation.pathname !== nextLocation.pathname;
  },
  [isDirty]
);

const blocker = useBlocker(shouldBlock);
```

### State transitions

```
unblocked --[navigation attempted while shouldBlock=true]--> blocked
blocked --[proceed()]--> proceeding --> unblocked
blocked --[reset()]--> unblocked
```

## Related

* [`useNavigate`](/api/hooks/use-navigate) - Navigate programmatically
* [`useNavigation`](/api/hooks/use-navigation) - Track navigation state
* [`Form`](/api/components/form) - Form component
