Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2: remove CatchBoundary + v2_errorBoundary #6906

Merged
merged 6 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/v2-remove-catch-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@remix-run/dev": major
"@remix-run/react": major
"@remix-run/server-runtime": major
"@remix-run/testing": major
---

Remove `v2_errorBoundary` flag and `CatchBoundary` implementation
5 changes: 0 additions & 5 deletions docs/api/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,6 @@ title: Conventions

[Moved →][moved-41]

### CatchBoundary

[Moved →][moved-42]

### ErrorBoundary

[Moved →][moved-43]
Expand Down Expand Up @@ -238,7 +234,6 @@ title: Conventions
[moved-39]: ../route/links
[moved-40]: ../route/links#htmllinkdescriptor
[moved-41]: ../route/links#pagelinkdescriptor
[moved-42]: ../route/catch-boundary
[moved-43]: ../route/error-boundary
[moved-44]: ../route/handle
[moved-48]: ../other-api/asset-imports
3 changes: 1 addition & 2 deletions docs/file-conventions/routes-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ app/
The file in `app/root.tsx` is your root layout, or "root route" (very sorry for those of you who pronounce those words the same way!). It works just like all other routes:

- You can export a [`loader`][loader], [`action`][action], [`meta`][meta], [`headers`][headers], or [`links`][links] function
- You can export an [`ErrorBoundary`][error-boundary] or [`CatchBoundary`][catch-boundary]
- You can export an [`ErrorBoundary`][error-boundary]
- Your default export is the layout component that renders the rest of your app in an [`<Outlet />`][outlet]

## Basic Routes
Expand Down Expand Up @@ -306,7 +306,6 @@ Because some characters have special meaning, you must use our escaping syntax i
[headers]: ../route/headers
[links]: ../route/links
[error-boundary]: ../route/error-boundary
[catch-boundary]: ../route/catch-boundary
[outlet]: ../components/outlet
[view-example-app]: https://github.com/remix-run/examples/tree/main/multiple-params
[use-params]: https://reactrouter.com/hooks/use-params
Expand Down
7 changes: 3 additions & 4 deletions docs/guides/data-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ One of the primary features of Remix is simplifying interactions with the server
- Ensure the data in the UI is in sync with the data on the server by revalidating after [actions][action]
- Excellent scroll restoration on back/forward clicks (even across domains)
- Handle server-side errors with [error boundaries][error-boundary]
- Enable solid UX for "Not Found" and "Unauthorized" with [catch boundaries][catch-boundary]
- Enable solid UX for "Not Found" and "Unauthorized" with [error boundaries][error-boundary]
- Help you keep the happy path of your UI happy

## Basics
Expand Down Expand Up @@ -257,7 +257,7 @@ export default function Product() {

## Not Found

While loading data it's common for a record to be "not found". As soon as you know you can't render the component as expected, `throw` a response and Remix will stop executing code in the current loader and switch over to the nearest [catch boundary][catch-boundary].
While loading data it's common for a record to be "not found". As soon as you know you can't render the component as expected, `throw` a response and Remix will stop executing code in the current loader and switch over to the nearest [error boundary][error-boundary].

```tsx lines=[10-13]
export const loader = async ({
Expand Down Expand Up @@ -684,7 +684,7 @@ That said, if you bring an external data library and sidestep Remix's own data c
- Ensure the data in the UI is in sync with the data on the server by revalidating after actions
- Excellent scroll restoration on back/forward clicks (even across domains)
- Handle server-side errors with [error boundaries][error-boundary]
- Enable solid UX for "Not Found" and "Unauthorized" with [catch boundaries][catch-boundary]
- Enable solid UX for "Not Found" and "Unauthorized" with [error boundaries][error-boundary]
- Help you keep the happy path of your UI happy.

Instead you'll need to do extra work to provide a good user experience.
Expand Down Expand Up @@ -736,7 +736,6 @@ export default function RouteComp() {
```

[action]: ../route/action
[catch-boundary]: ../route/catch-boundary
[cloudflare-kv-setup]: https://developers.cloudflare.com/workers/cli-wrangler/commands#kv
[cloudflare-kv]: https://developers.cloudflare.com/workers/learning/how-kv-works
[error-boundary]: ../route/error-boundary
Expand Down
69 changes: 10 additions & 59 deletions docs/guides/not-found.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,19 @@ export async function loader({ params }: LoaderArgs) {
}
```

Remix will catch the response and send your app down the [Catch Boundary][catch-boundary] path. It's actually exactly like Remix's automatic [error handling][errors], but instead of exporting an `ErrorBoundary`, you export a `CatchBoundary`.
Remix will catch the response and send your app down the [Error Boundary][error-boundary] path. It's actually exactly like Remix's automatic [error handling][errors], but instead of receiving an `Error` from `useRouteError()`, you'll receive an object with your response `status`, `statusText`, and extracted `data`.

What's nice about throwing a response is that code in your loader _stops executing_. The rest of your code doesn't have to deal with the chance that the page is defined or not (this is especially handy for TypeScript).

Throwing also ensures that your route component doesn't render if the loader wasn't successful. Your route components only have to consider the "happy path". They don't need pending states, error states, or in our case here, not-found states.

## Root Catch Boundary
## Root Error Boundary

You probably already have one at the root of your app. This will handle all thrown responses that weren't handled in a nested route (more on that in a sec). Here's a sample:
You probably already have one at the root of your app. This will handle all thrown responses that weren't handled in a nested route. Here's a sample:

```tsx
export function CatchBoundary() {
const caught = useCatch();
export function ErrorBoundary() {
const error = useRouteError();
return (
<html>
<head>
Expand All @@ -56,7 +56,11 @@ export function CatchBoundary() {
</head>
<body>
<h1>
{caught.status} {caught.statusText}
{isRouteErrorResponse(error)
? `${error.status} ${error.statusText}`
: error instanceof Error
? error.message
: "Unknown Error"}
</h1>
<Scripts />
</body>
Expand All @@ -65,59 +69,6 @@ export function CatchBoundary() {
}
```

## Nested Catch Boundaries

Just like [errors], nested routes can export their own catch boundary to handle the 404 UI without taking down all of the parent layouts around it, and add some nice UX touches right in context. Bots are happy, SEO is happy, CDNs are happy, users are happy, and your code stays in context, so it seems like everybody involved is happy with this.

```tsx filename=app/routes/pages/$pageId.tsx
import type { LoaderArgs } from "@remix-run/node"; // or cloudflare/deno
import {
Form,
useLoaderData,
useParams,
} from "@remix-run/react";

export async function loader({ params }: LoaderArgs) {
const page = await db.page.findOne({
where: { slug: params.slug },
});

if (!page) {
throw new Response(null, {
status: 404,
statusText: "Not Found",
});
}

return json(page);
}

export function CatchBoundary() {
const params = useParams();
return (
<div>
<h2>We couldn't find that page!</h2>
<Form action="../create">
<button
type="submit"
name="slug"
value={params.slug}
>
Create {params.slug}?
</button>
</Form>
</div>
);
}

export default function Page() {
return <PageView page={useLoaderData<typeof loader>()} />;
}
```

As you can probably tell, this mechanism isn't just limited to 404s. You can throw any response from a loader or action to send your app down the catch boundary path. For more information, check out the [Catch Boundary][catch-boundary] docs.

[catch-boundary]: ../route/catch-boundary
[errors]: ./errors
[404-status-code]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
[splat-route]: ./routing#splats
13 changes: 6 additions & 7 deletions docs/pages/api-development-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,12 @@ The lifecycle is thus either:

## Current Future Flags

| Flag | Description |
| -------------------- | --------------------------------------------------------------------- |
| `v2_dev` | Enable the new development server (including HMR/HDR support) |
| `v2_errorBoundary` | Combine `ErrorBoundary`/`CatchBoundary` into a single `ErrorBoundary` |
| `v2_headers` | Leverage ancestor `headers` if children do not export `headers` |
| `v2_meta` | Enable the new API for your `meta` functions |
| `v2_routeConvention` | Enable the flat routes style of file-based routing |
| Flag | Description |
| -------------------- | --------------------------------------------------------------- |
| `v2_dev` | Enable the new development server (including HMR/HDR support) |
| `v2_headers` | Leverage ancestor `headers` if children do not export `headers` |
| `v2_meta` | Enable the new API for your `meta` functions |
| `v2_routeConvention` | Enable the flat routes style of file-based routing |

[future-flags-blog-post]: https://remix.run/blog/future-flags
[feature-flowchart]: /docs-images/feature-flowchart.png
7 changes: 0 additions & 7 deletions docs/pages/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,4 @@ Again, `formData.getAll()` is often all you need, we encourage you to give it a
[form-data]: https://developer.mozilla.org/en-US/docs/Web/API/FormData
[query-string]: https://www.npmjs.com/package/query-string
[ramda]: https://www.npmjs.com/package/ramda

## What's the difference between `CatchBoundary` & `ErrorBoundary`?

Error boundaries render when your application throws an error and you had no clue it was going to happen. Most apps just go blank or have spinners spin forever. In remix the error boundary renders and you have granular control over it.

Catch boundaries render when you decide in a loader that you can't proceed down the happy path to render the UI you want (auth required, record not found, etc.), so you throw a response and let some catch boundary up the tree handle it.

[watch-on-you-tube]: https://www.youtube.com/watch?v=w2i-9cYxSdc&ab_channel=Remix
4 changes: 1 addition & 3 deletions docs/pages/v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,7 @@ The [meta v2][meta-v2] docs have more tips on merging route meta.
```js filename=remix.config.js
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
future: {
v2_errorBoundary: true,
},
future: {},
};
```

Expand Down
36 changes: 0 additions & 36 deletions docs/route/catch-boundary.md

This file was deleted.

67 changes: 0 additions & 67 deletions docs/route/error-boundary-v2.md

This file was deleted.

55 changes: 40 additions & 15 deletions docs/route/error-boundary.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,55 @@
---
title: ErrorBoundary
new: true
---

# `ErrorBoundary`

<docs-warning>The behaviors of `CatchBoundary` and `ErrorBoundary` are changing in v2. You can prepare for this change at your convenience with the `v2_errorBoundary` future flag. For instructions on making this change see the [v2 guide][v2guide].</docs-warning>
A Remix `ErrorBoundary` component works just like normal React [error boundaries][error-boundaries], but with a few extra capabilities. When there is an error in your route component, the `ErrorBoundary` will be rendered in its place, nested inside any parent routes. `ErrorBoundary` components also render when there is an error in the `loader` or `action` functions for a route, so all errors for that route may be handled in one spot.

An `ErrorBoundary` is a React component that renders whenever there is an error anywhere on the route, either during rendering or during data loading. We use the word "error" to mean an uncaught exception; something you didn't anticipate happening. You can intentionally throw a `Response` to render the `CatchBoundary`, but everything else that is thrown is handled by the `ErrorBoundary`.
The most common use-cases tend to be:

A Remix `ErrorBoundary` component works just like normal React [error boundaries][error-boundaries], but with a few extra capabilities. When there is an error in your route component, the `ErrorBoundary` will be rendered in its place, nested inside any parent routes. `ErrorBoundary` components also render when there is an error in the `loader` or `action` functions for a route, so all errors for that route may be handled in one spot.
- You may intentionally throw a 4xx `Response` to trigger an error UI
- Throwing a 400 on bad user input
- Throwing a 401 for unauthorized access
- Throwing a 404 when you can't find requested data
- React may unintentionally throw an `Error` if it encounters a runtime error during rendering

An `ErrorBoundary` component receives one prop: the `error` that occurred.
To obtain the thrown object, you can use the [`useRouteError`][use-route-error] hook. When a `Response` is thrown, it will be automatically unwrapped into an `ErrorResponse` instance with `state`/`statusText`/`data` fields so that you don't need to bother with `await response.json()` in your component. To differentiate thrown `Response`'s from thrown `Error`'s' you can use the [`isRouteErrorResponse`][is-route-error-response] utility.

```tsx
export function ErrorBoundary({ error }) {
return (
<div>
<h1>Error</h1>
<p>{error.message}</p>
<p>The stack trace is:</p>
<pre>{error.stack}</pre>
</div>
);
import {
isRouteErrorResponse,
useRouteError,
} from "@remix-run/react";

export function ErrorBoundary() {
const error = useRouteError();

if (isRouteErrorResponse(error)) {
return (
<div>
<h1>
{error.status} {error.statusText}
</h1>
<p>{error.data}</p>
</div>
);
} else if (error instanceof Error) {
return (
<div>
<h1>Error</h1>
<p>{error.message}</p>
<p>The stack trace is:</p>
<pre>{error.stack}</pre>
</div>
);
} else {
return <h1>Unknown Error</h1>;
}
}
```

[error-boundaries]: https://reactjs.org/docs/error-boundaries.html
[error-boundary-v2]: ./error-boundary-v2
[v2guide]: ../pages/v2#catchboundary-and-errorboundary
[use-route-error]: ../hooks/use-route-error
[is-route-error-response]: ../utils/is-route-error-response
Loading