Skip to content

Commit

Permalink
Merge branch 'dev' into brophdawg11/flushsync
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 committed Nov 14, 2023
2 parents d3dc66a + cf9b507 commit a330b6e
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/stabilize-use-blocker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@remix-run/react": minor
---

Remove the `unstable_` prefix from the [`useBlocker`](https://reactrouter.com/en/main/hooks/use-blocker) hook as it's been in use for enough time that we are confident in the API. We do not plan to remove the prefix from `unstable_usePrompt` due to differences in how browsers handle `window.confirm` that prevent React Router from guaranteeing consistent/correct behavior.
82 changes: 82 additions & 0 deletions docs/hooks/use-blocker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
title: useBlocker
---

# `useBlocker`

The `useBlocker` hook allows you to prevent the user from navigating away from the current location, and present them with a custom UI to allow them to confirm the navigation.

<docs-info>
This only works for client-side navigations within your React Router application and will not block document requests. To prevent document navigations you will need to add your own <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event" target="_blank">`beforeunload`</a> event handler.
</docs-info>

<docs-warning>
Blocking a user from navigating is a bit of an anti-pattern, so please carefully consider any usage of this hook and use it sparingly. In the de-facto use case of preventing a user navigating away from a half-filled form, you might consider persisting unsaved state to `sessionStorage` and automatically re-filling it if they return instead of blocking them from navigating away.
</docs-warning>

```tsx
function ImportantForm() {
const [value, setValue] = React.useState("");

// Block navigating elsewhere when data has been entered into the input
const blocker = useBlocker(
({ currentLocation, nextLocation }) =>
value !== "" &&
currentLocation.pathname !== nextLocation.pathname
);

return (
<Form method="post">
<label>
Enter some important data:
<input
name="data"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
</label>
<button type="submit">Save</button>

{blocker.state === "blocked" ? (
<div>
<p>Are you sure you want to leave?</p>
<button onClick={() => blocker.proceed()}>
Proceed
</button>
<button onClick={() => blocker.reset()}>
Cancel
</button>
</div>
) : null}
</Form>
);
}
```

For a more complete example, please refer to the [example][example] in the repository.

## Properties

### `state`

The current state of the blocker

- `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

### `location`

When in a `blocked` state, this represents the location to which we blocked a navigation. When in a `proceeding` state, this is the location being navigated to after a `blocker.proceed()` call.

## Methods

### `proceed()`

When in a `blocked` state, you may call `blocker.proceed()` to proceed to the blocked location.

### `reset()`

When in a `blocked` state, you may call `blocker.reset()` to return the blocker back to an `unblocked` state and leave the user at the current location.

[example]: https://github.com/remix-run/react-router/tree/main/examples/navigation-blocking
49 changes: 49 additions & 0 deletions docs/hooks/use-prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
title: unstable_usePrompt
---

# `unstable_usePrompt`

The `unstable_usePrompt` hook allows you to prompt the user for confirmation via [`window.confirm`][window-confirm] prior to navigating away from the current location.

<docs-info>
This only works for client-side navigations within your React Router application and will not block document requests. To prevent document navigations you will need to add your own <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event" target="_blank">`beforeunload`</a> event handler.
</docs-info>

<docs-warning>
Blocking a user from navigating is a bit of an anti-pattern, so please carefully consider any usage of this hook and use it sparingly. In the de-facto use case of preventing a user navigating away from a half-filled form, you might consider persisting unsaved state to `sessionStorage` and automatically re-filling it if they return instead of blocking them from navigating away.
</docs-warning>

<docs-warning>
We do not plan to remove the `unstable_` prefix from this hook because the behavior is non-deterministic across browsers when the prompt is open, so React Router cannot guarantee correct behavior in all scenarios. To avoid this non-determinism, we recommend using `useBlocker` instead which also gives you control over the confirmation UX.
</docs-warning>

```tsx
function ImportantForm() {
const [value, setValue] = React.useState("");

// Block navigating elsewhere when data has been entered into the input
unstable_usePrompt({
message: "Are you sure?",
when: ({ currentLocation, nextLocation }) =>
value !== "" &&
currentLocation.pathname !== nextLocation.pathname,
});

return (
<Form method="post">
<label>
Enter some important data:
<input
name="data"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
</label>
<button type="submit">Save</button>
</Form>
);
}
```

[window-confirm]: https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm
4 changes: 2 additions & 2 deletions integration/form-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ test.describe("Forms", () => {
await app.goto("/projects/blarg");
let html = await app.getHtml();
let el = getElement(html, `#${SPLAT_ROUTE_NO_ACTION}`);
expect(el.attr("action")).toMatch("/projects");
expect(el.attr("action")).toMatch("/projects/blarg");
});

test("no action resolves to URL including search params", async ({
Expand All @@ -931,7 +931,7 @@ test.describe("Forms", () => {
await app.goto("/projects/blarg?foo=bar");
let html = await app.getHtml();
let el = getElement(html, `#${SPLAT_ROUTE_NO_ACTION}`);
expect(el.attr("action")).toMatch("/projects?foo=bar");
expect(el.attr("action")).toMatch("/projects/blarg?foo=bar");
});

test("absolute action resolves relative to the root route", async ({
Expand Down
7 changes: 1 addition & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10917,12 +10917,7 @@ pumpify@^1.3.3:
inherits "^2.0.3"
pump "^2.0.0"

punycode@^2.1.0, punycode@^2.1.1:
version "2.3.0"
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==

punycode@^2.3.0:
punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0:
version "2.3.1"
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
Expand Down

0 comments on commit a330b6e

Please sign in to comment.