diff --git a/integration/error-boundary-test.ts b/integration/error-boundary-test.ts index a71a3a38b3..fbe134442b 100644 --- a/integration/error-boundary-test.ts +++ b/integration/error-boundary-test.ts @@ -655,256 +655,6 @@ test.describe("ErrorBoundary", () => { }); }); -test.describe("loaderData in ErrorBoundary", () => { - let fixture: Fixture; - let appFixture: AppFixture; - let consoleErrors: string[]; - let oldConsoleError: () => void; - - test.beforeAll(async () => { - fixture = await createFixture({ - files: { - "app/root.tsx": js` - import { Links, Meta, Outlet, Scripts } from "react-router"; - - export default function Root() { - return ( - - - - - - -
- -
- - - - ); - } - `, - - "app/routes/parent.tsx": js` - import { Outlet, useLoaderData, useMatches, useRouteError } from "react-router"; - - export function loader() { - return "PARENT"; - } - - export default function () { - return ( -
-

{useLoaderData()}

- -
- ) - } - - export function ErrorBoundary() { - let error = useRouteError(); - return ( - <> -

{useLoaderData()}

-

- {useMatches().find(m => m.id === 'routes/parent').data} -

-

{error.message}

- - ); - } - `, - - "app/routes/parent.child-with-boundary.tsx": js` - import { Form, useLoaderData, useRouteError } from "react-router"; - - export function loader() { - return "CHILD"; - } - - export function action() { - throw new Error("Broken!"); - } - - export default function () { - return ( - <> -

{useLoaderData()}

-
- -
- - ) - } - - export function ErrorBoundary() { - let error = useRouteError(); - return ( - <> -

{useLoaderData()}

-

{error.message}

- - ); - } - `, - - "app/routes/parent.child-without-boundary.tsx": js` - import { Form, useLoaderData } from "react-router"; - - export function loader() { - return "CHILD"; - } - - export function action() { - throw new Error("Broken!"); - } - - export default function () { - return ( - <> -

{useLoaderData()}

-
- -
- - ) - } - `, - }, - }); - - appFixture = await createAppFixture(fixture, ServerMode.Development); - }); - - test.afterAll(() => { - appFixture.close(); - }); - - test.beforeEach(({ page }) => { - oldConsoleError = console.error; - console.error = () => {}; - consoleErrors = []; - // Listen for all console events and handle errors - page.on("console", (msg) => { - if (msg.type() === "error") { - consoleErrors.push(msg.text()); - } - }); - }); - - test.afterEach(() => { - console.error = oldConsoleError; - }); - - test.describe("without JavaScript", () => { - test.use({ javaScriptEnabled: false }); - runBoundaryTests(); - }); - - test.describe("with JavaScript", () => { - test.use({ javaScriptEnabled: true }); - runBoundaryTests(); - }); - - function runBoundaryTests() { - test("Prevents useLoaderData in self ErrorBoundary", async ({ - page, - javaScriptEnabled, - }) => { - let app = new PlaywrightFixture(appFixture, page); - await app.goto("/parent/child-with-boundary"); - - expect(await app.getHtml("#parent-data")).toEqual( - '

PARENT

' - ); - expect(await app.getHtml("#child-data")).toEqual( - '

CHILD

' - ); - expect(consoleErrors).toEqual([]); - - await app.clickSubmitButton("/parent/child-with-boundary"); - await page.waitForSelector("#child-error"); - - expect(await app.getHtml("#child-error")).toEqual( - '

Broken!

' - ); - expect(await app.getHtml("#parent-data")).toEqual( - '

PARENT

' - ); - expect(await app.getHtml("#child-data")).toEqual( - '

' - ); - - // Only look for this message. Chromium browsers will also log the - // network error but firefox does not - // "Failed to load resource: the server responded with a status of 500 (Internal Server Error)", - let msg = - "You cannot `useLoaderData` in an errorElement (routeId: routes/parent.child-with-boundary)"; - if (javaScriptEnabled) { - expect(consoleErrors.filter((m) => m === msg)).toEqual([msg]); - } else { - // We don't get the useLoaderData message in the client when JS is disabled - expect(consoleErrors.filter((m) => m === msg)).toEqual([]); - } - }); - - test("Prevents useLoaderData in bubbled ErrorBoundary", async ({ - page, - javaScriptEnabled, - }) => { - let app = new PlaywrightFixture(appFixture, page); - await app.goto("/parent/child-without-boundary"); - - expect(await app.getHtml("#parent-data")).toEqual( - '

PARENT

' - ); - expect(await app.getHtml("#child-data")).toEqual( - '

CHILD

' - ); - expect(consoleErrors).toEqual([]); - - await app.clickSubmitButton("/parent/child-without-boundary"); - await page.waitForSelector("#parent-error"); - - expect(await app.getHtml("#parent-error")).toEqual( - '

Broken!

' - ); - if (javaScriptEnabled) { - // This data remains in single fetch with JS because we don't revalidate - // due to the 500 action response - expect(await app.getHtml("#parent-matches-data")).toEqual( - '

PARENT

' - ); - } else { - // But without JS document requests call all loaders up to the - // boundary route so parent's data clears out - expect(await app.getHtml("#parent-matches-data")).toEqual( - '

' - ); - } - expect(await app.getHtml("#parent-data")).toEqual( - '

' - ); - - // Only look for this message. Chromium browsers will also log the - // network error but firefox does not - // "Failed to load resource: the server responded with a status of 500 (Internal Server Error)", - let msg = - "You cannot `useLoaderData` in an errorElement (routeId: routes/parent)"; - if (javaScriptEnabled) { - expect(consoleErrors.filter((m) => m === msg)).toEqual([msg]); - } else { - // We don't get the useLoaderData message in the client when JS is disabled - expect(consoleErrors.filter((m) => m === msg)).toEqual([]); - } - }); - } -}); - test.describe("Default ErrorBoundary", () => { let fixture: Fixture; let appFixture: AppFixture; diff --git a/packages/react-router/__tests__/data-memory-router-test.tsx b/packages/react-router/__tests__/data-memory-router-test.tsx index c1d807d97e..3375d47fde 100644 --- a/packages/react-router/__tests__/data-memory-router-test.tsx +++ b/packages/react-router/__tests__/data-memory-router-test.tsx @@ -2476,181 +2476,6 @@ describe("createMemoryRouter", () => { " `); }); - - it("does not allow loaderData usage in self-caught error boundaries", async () => { - let errorSpy = jest.spyOn(console, "error"); - - let router = createMemoryRouter( - createRoutesFromElements( - }> - Promise.reject(new Error("Kaboom!"))} - element={

Foo

} - errorElement={} - /> -
- ) - ); - let { container } = render(); - - function Layout() { - let navigation = useNavigation(); - return ( -
- Link to Foo -

{navigation.state}

- -
- ); - } - - function FooError() { - let error = useRouteError() as Error; - let data = useLoaderData() as { message: string }; - return ( - <> -

- Foo Data:{data === undefined ? "undefined" : JSON.stringify(data)} -

-

Foo Error:{error.message}

- - ); - } - - expect(getHtml(container)).toMatchInlineSnapshot(` - "
-
- - Link to Foo - -

- idle -

-
-
" - `); - - fireEvent.click(screen.getByText("Link to Foo")); - await waitFor(() => screen.getByText("Foo Error:Kaboom!")); - expect(getHtml(container)).toMatchInlineSnapshot(` - "
-
- - Link to Foo - -

- idle -

-

- Foo Data: - undefined -

-

- Foo Error: - Kaboom! -

-
-
" - `); - - expect(errorSpy).toHaveBeenCalledWith( - "You cannot `useLoaderData` in an errorElement (routeId: 0-0)" - ); - errorSpy.mockRestore(); - }); - - it("does not allow useLoaderData usage in bubbled error boundaries", async () => { - let errorSpy = jest.spyOn(console, "error"); - - let router = createMemoryRouter( - createRoutesFromElements( - } - loader={() => "ROOT"} - errorElement={} - > - Promise.reject(new Error("Kaboom!"))} - element={

Foo

} - /> -
- ), - { - hydrationData: { - loaderData: { - "0": "ROOT", - }, - }, - } - ); - let { container } = render(); - - function Layout() { - let navigation = useNavigation(); - return ( -
- Link to Foo -

{navigation.state}

- -
- ); - } - function LayoutError() { - let data = useLoaderData() as { message: string }; - let error = useRouteError() as Error; - return ( - <> -

- Layout Data: - {data === undefined ? "undefined" : JSON.stringify(data)} -

-

Layout Error:{error.message}

- - ); - } - - expect(getHtml(container)).toMatchInlineSnapshot(` - "
-
- - Link to Foo - -

- idle -

-
-
" - `); - - fireEvent.click(screen.getByText("Link to Foo")); - await waitFor(() => screen.getByText("Layout Error:Kaboom!")); - expect(getHtml(container)).toMatchInlineSnapshot(` - "
-

- Layout Data: - undefined -

-

- Layout Error: - Kaboom! -

-
" - `); - - expect(errorSpy).toHaveBeenCalledWith( - "You cannot `useLoaderData` in an errorElement (routeId: 0)" - ); - errorSpy.mockRestore(); - }); }); describe("defer", () => { diff --git a/packages/react-router/lib/hooks.tsx b/packages/react-router/lib/hooks.tsx index f6adc4ab69..dab9f51569 100644 --- a/packages/react-router/lib/hooks.tsx +++ b/packages/react-router/lib/hooks.tsx @@ -1085,13 +1085,6 @@ export function useMatches(): UIMatch[] { export function useLoaderData(): unknown { let state = useDataRouterState(DataRouterStateHook.UseLoaderData); let routeId = useCurrentRouteId(DataRouterStateHook.UseLoaderData); - - if (state.errors && state.errors[routeId] != null) { - console.error( - `You cannot \`useLoaderData\` in an errorElement (routeId: ${routeId})` - ); - return undefined; - } return state.loaderData[routeId]; }