diff --git a/.changeset/three-cheetahs-lick.md b/.changeset/three-cheetahs-lick.md new file mode 100644 index 00000000000..3a6d06828e3 --- /dev/null +++ b/.changeset/three-cheetahs-lick.md @@ -0,0 +1,7 @@ +--- +"@remix-run/dev": patch +"@remix-run/react": patch +"@remix-run/server-runtime": patch +--- + +Deprecate `CatchBoundary` in favor of `future.v2_errorBoundary` diff --git a/docs/route/catch-boundary.md b/docs/route/catch-boundary.md index bd5985a8d58..556fdc64d9b 100644 --- a/docs/route/catch-boundary.md +++ b/docs/route/catch-boundary.md @@ -4,6 +4,8 @@ title: CatchBoundary # `CatchBoundary` +The separation of `CatchBoundary` and `ErrorBoundary` has been deprecated and Remix v2 will use a singular `ErrorBoundary` for all thrown Responses and Errors. It is recommended that you opt-into the new behavior in Remix v1 via the `future.v2_errorBoundary` flag in your `remix.config.js` file. Please refer to the [ErrorBoundary (v2)][error-boundary-v2] docs for more information. + A `CatchBoundary` is a React component that renders whenever an action or loader throws a `Response`. **Note:** We use the word "catch" to represent the codepath taken when a `Response` type is thrown; you thought about bailing from the "happy path". This is different from an uncaught error you did not expect to occur. @@ -29,3 +31,5 @@ export function CatchBoundary() { ); } ``` + +[error-boundary-v2]: ./error-boundary-v2 diff --git a/docs/route/error-boundary-v2.md b/docs/route/error-boundary-v2.md new file mode 100644 index 00000000000..c1a03390587 --- /dev/null +++ b/docs/route/error-boundary-v2.md @@ -0,0 +1,11 @@ +--- +title: ErrorBoundary (v2) +--- + +# `ErrorBoundary (v2)` + +You can opt into the Remix v2 `ErrorBoundary` behavior via the `future.v2_errorBoundary` flag in your `remix.config.js` + +If you export an `ErrorBoundary` component from your route module, it will be used as the React Router [`errorElement`][rr-error-element] and will render if you throw from a loader/action or if React throws during rendering your Route component. + +[rr-error-element]: https://reactrouter.com/route/error-element diff --git a/docs/route/error-boundary.md b/docs/route/error-boundary.md index 8266833e628..5b8e9118a0d 100644 --- a/docs/route/error-boundary.md +++ b/docs/route/error-boundary.md @@ -4,6 +4,8 @@ title: ErrorBoundary # `ErrorBoundary` +The separation of `CatchBoundary` and `ErrorBoundary` has been deprecated and Remix v2 will use a singular `ErrorBoundary` for all thrown Responses and Errors. It is recommended that you opt-into the new behavior in Remix v1 via the `future.v2_errorBoundary` flag in your `remix.config.js` file. Please refer to the [ErrorBoundary (v2)][error-boundary-v2] docs for more information. + An `ErrorBoundary` is a React component that renders whenever there is an error anywhere on the route, either during rendering or during data loading. **Note:** We use the word "error" to mean an uncaught exception; something you didn't anticipate happening. This is different from other types of "errors" that you are able to recover from easily, for example a 404 error where you can still show something in the user interface to indicate you weren't able to find some data. @@ -26,3 +28,4 @@ export function ErrorBoundary({ error }) { ``` [error-boundaries]: https://reactjs.org/docs/error-boundaries.html +[error-boundary-v2]: ./error-boundary-v2 diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts index 8686777438a..af87d1c55ec 100644 --- a/packages/remix-dev/config.ts +++ b/packages/remix-dev/config.ts @@ -401,6 +401,10 @@ export async function readConfig( warnOnce(serverBuildTargetWarning, "v2_serverBuildTarget"); } + if (!appConfig.future?.v2_errorBoundary) { + warnOnce(errorBoundaryWarning, "v2_errorBoundary"); + } + let isCloudflareRuntime = ["cloudflare-pages", "cloudflare-workers"].includes( appConfig.serverBuildTarget ?? "" ); @@ -726,11 +730,27 @@ const resolveServerBuildPath = ( // @ts-expect-error available in node 12+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat#browser_compatibility -let listFormat = new Intl.ListFormat("en", { +const listFormat = new Intl.ListFormat("en", { style: "long", type: "conjunction", }); -export let serverBuildTargetWarning = `⚠️ DEPRECATED: The "serverBuildTarget" config option is deprecated. Use a combination of "publicPath", "serverBuildPath", "serverConditions", "serverDependenciesToBundle", "serverMainFields", "serverMinify", "serverModuleFormat" and/or "serverPlatform" instead.`; - -export let flatRoutesWarning = `⚠️ DEPRECATED: The old nested folders route convention has been deprecated in favor of "flat routes". Please enable the new routing convention via the \`future.v2_routeConvention\` flag in your \`remix.config.js\` file. For more information, please see https://remix.run/docs/en/main/file-conventions/route-files-v2.`; +export const serverBuildTargetWarning = + '⚠️ DEPRECATED: The "serverBuildTarget" config option is deprecated. Use a ' + + 'combination of "publicPath", "serverBuildPath", "serverConditions", ' + + '"serverDependenciesToBundle", "serverMainFields", "serverMinify", ' + + '"serverModuleFormat" and/or "serverPlatform" instead.'; + +export const flatRoutesWarning = + "⚠️ DEPRECATED: The old nested folders route convention has been deprecated " + + 'in favor of "flat routes". Please enable the new routing convention via the ' + + "`future.v2_routeConvention` flag in your `remix.config.js` file. For more " + + "information, please see https://remix.run/docs/en/main/file-conventions/route-files-v2."; + +export const errorBoundaryWarning = + "⚠️ DEPRECATED: The separation of `CatchBoundary` and `ErrorBoundary` has " + + "been deprecated and Remix v2 will use a singular `ErrorBoundary` for " + + "all thrown values (`Response` and `Error`). Please migrate to the new " + + "behavior in Remix v1 via the `future.v2_errorBoundary` flag in your " + + "`remix.config.js` file. For more information, see " + + "https://remix.run/docs/route/error-boundary-v2"; diff --git a/packages/remix-react/browser.tsx b/packages/remix-react/browser.tsx index fb3d1f153f9..70eeb7095d5 100644 --- a/packages/remix-react/browser.tsx +++ b/packages/remix-react/browser.tsx @@ -14,6 +14,7 @@ import { import { deserializeErrors } from "./errors"; import type { RouteModules } from "./routeModules"; import { createClientRoutes } from "./routes"; +import { warnOnce } from "./warnings"; /* eslint-disable prefer-let/prefer-let */ declare global { @@ -138,6 +139,18 @@ if (import.meta && import.meta.hot) { */ export function RemixBrowser(_props: RemixBrowserProps): ReactElement { if (!router) { + if (!window.__remixContext.future.v2_errorBoundary) { + warnOnce( + false, + "⚠️ DEPRECATED: The separation of `CatchBoundary` and `ErrorBoundary` has " + + "been deprecated and Remix v2 will use a singular `ErrorBoundary` for " + + "all thrown values (`Response` and `Error`). Please migrate to the new " + + "behavior in Remix v1 via the `future.v2_errorBoundary` flag in your " + + "`remix.config.js` file. For more information, see " + + "https://remix.run/docs/route/error-boundary-v2" + ); + } + let routes = createClientRoutes( window.__remixManifest.routes, window.__remixRouteModules, diff --git a/packages/remix-react/errorBoundaries.tsx b/packages/remix-react/errorBoundaries.tsx index faa50dd4193..5e083af4d36 100644 --- a/packages/remix-react/errorBoundaries.tsx +++ b/packages/remix-react/errorBoundaries.tsx @@ -133,6 +133,8 @@ let RemixCatchContext = React.createContext( /** * Returns the status code and thrown response data. * + * @deprecated Please enable the v2_errorBoundary flag + * * @see https://remix.run/route/catch-boundary */ export function useCatch< diff --git a/packages/remix-react/routeModules.ts b/packages/remix-react/routeModules.ts index 785add903db..147d799bdff 100644 --- a/packages/remix-react/routeModules.ts +++ b/packages/remix-react/routeModules.ts @@ -32,6 +32,8 @@ export interface RouteModule { /** * A React component that is rendered when the server throws a Response. * + * @deprecated Please enable the v2_errorBoundary flag + * * @see https://remix.run/route/catch-boundary */ export type CatchBoundaryComponent = ComponentType<{}>; @@ -39,6 +41,8 @@ export type CatchBoundaryComponent = ComponentType<{}>; /** * A React component that is rendered when there is an error on a route. * + * @deprecated Please enable the v2_errorBoundary flag + * * @see https://remix.run/route/error-boundary */ export type ErrorBoundaryComponent = ComponentType<{ error: Error }>; diff --git a/packages/remix-server-runtime/routeModules.ts b/packages/remix-server-runtime/routeModules.ts index 380b2549d1f..25a7bb19820 100644 --- a/packages/remix-server-runtime/routeModules.ts +++ b/packages/remix-server-runtime/routeModules.ts @@ -36,14 +36,25 @@ export interface ActionFunction { /** * A React component that is rendered when the server throws a Response. + * + * @deprecated Please enable the v2_errorBoundary flag */ export type CatchBoundaryComponent = ComponentType; /** * A React component that is rendered when there is an error on a route. + * + * @deprecated Please enable the v2_errorBoundary flag */ export type ErrorBoundaryComponent = ComponentType<{ error: Error }>; +/** + * V2 version of the ErrorBoundary that eliminates the distinction between + * Error and Catch Boundaries and behaves like RR 6.4 errorElement and captures + * errors with useRouteError() + */ +export type V2_ErrorBoundaryComponent = ComponentType; + /** * A function that returns HTTP headers to be used for a route. These headers * will be merged with (and take precedence over) headers from parent routes. @@ -224,7 +235,7 @@ export type RouteHandle = any; export interface EntryRouteModule { CatchBoundary?: CatchBoundaryComponent; - ErrorBoundary?: ErrorBoundaryComponent; + ErrorBoundary?: ErrorBoundaryComponent | V2_ErrorBoundaryComponent; default: RouteComponent; handle?: RouteHandle; links?: LinksFunction;