Skip to content

Commit

Permalink
Allow useRevalidate to resolve a loader-driven error boundary UI (#10369
Browse files Browse the repository at this point in the history
)
  • Loading branch information
brophdawg11 committed Apr 21, 2023
1 parent 34779ab commit fda0123
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 148 deletions.
5 changes: 5 additions & 0 deletions .changeset/revalidate-error-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-router": patch
---

Allow `useRevalidator()` to resolve a loader-driven error boundary scenario
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@
"none": "45.8 kB"
},
"packages/react-router/dist/react-router.production.min.js": {
"none": "13.3 kB"
"none": "13.5 kB"
},
"packages/react-router/dist/umd/react-router.production.min.js": {
"none": "15.6 kB"
"none": "15.8 kB"
},
"packages/react-router-dom/dist/react-router-dom.production.min.js": {
"none": "12 kB"
Expand Down
277 changes: 135 additions & 142 deletions packages/react-router/__tests__/data-memory-router-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,7 @@ describe("createMemoryRouter", () => {
});
});

it("reloads data using useRevalidate", async () => {
it("reloads data using useRevalidator", async () => {
let count = 1;
let router = createMemoryRouter(
createRoutesFromElements(
Expand Down Expand Up @@ -1747,37 +1747,11 @@ describe("createMemoryRouter", () => {
);
let { container } = render(<RouterProvider router={router} />);

expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<h2>
Unexpected Application Error!
</h2>
<h3
style="font-style: italic;"
>
404 Not Found
</h3>
<p>
💿 Hey developer 👋
</p>
<p>
You can provide a way better UX than this when your app throws errors by providing your own
<code
style="padding: 2px 4px; background-color: rgba(200, 200, 200, 0.5);"
>
ErrorBoundary
</code>
or
<code
style="padding: 2px 4px; background-color: rgba(200, 200, 200, 0.5);"
>
errorElement
</code>
prop on your route.
</p>
</div>"
`);
let html = getHtml(container);
expect(html).toMatch("Unexpected Application Error!");
expect(html).toMatch("404 Not Found");
expect(html).toMatch("💿 Hey developer 👋");
expect(html).not.toMatch(/stack/i);
});

it("renders navigation errors with a default if no errorElements are provided", async () => {
Expand Down Expand Up @@ -1861,42 +1835,11 @@ describe("createMemoryRouter", () => {
error.stack = "FAKE STACK TRACE";
barDefer.reject(error);
await waitFor(() => screen.getByText("Kaboom!"));
expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<h2>
Unexpected Application Error!
</h2>
<h3
style="font-style: italic;"
>
Kaboom!
</h3>
<pre
style="padding: 0.5rem; background-color: rgba(200, 200, 200, 0.5);"
>
FAKE STACK TRACE
</pre>
<p>
💿 Hey developer 👋
</p>
<p>
You can provide a way better UX than this when your app throws errors by providing your own
<code
style="padding: 2px 4px; background-color: rgba(200, 200, 200, 0.5);"
>
ErrorBoundary
</code>
or
<code
style="padding: 2px 4px; background-color: rgba(200, 200, 200, 0.5);"
>
errorElement
</code>
prop on your route.
</p>
</div>"
`);
let html = getHtml(container);
expect(html).toMatch("Unexpected Application Error!");
expect(html).toMatch("Kaboom!");
expect(html).toMatch("FAKE STACK TRACE");
expect(html).toMatch("💿 Hey developer 👋");
});

// This test ensures that when manual routes are used, we add hasErrorBoundary
Expand Down Expand Up @@ -2095,42 +2038,11 @@ describe("createMemoryRouter", () => {
throw error;
}

expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<h2>
Unexpected Application Error!
</h2>
<h3
style="font-style: italic;"
>
Kaboom!
</h3>
<pre
style="padding: 0.5rem; background-color: rgba(200, 200, 200, 0.5);"
>
FAKE STACK TRACE
</pre>
<p>
💿 Hey developer 👋
</p>
<p>
You can provide a way better UX than this when your app throws errors by providing your own
<code
style="padding: 2px 4px; background-color: rgba(200, 200, 200, 0.5);"
>
ErrorBoundary
</code>
or
<code
style="padding: 2px 4px; background-color: rgba(200, 200, 200, 0.5);"
>
errorElement
</code>
prop on your route.
</p>
</div>"
`);
let html = getHtml(container);
expect(html).toMatch("Unexpected Application Error!");
expect(html).toMatch("Kaboom!");
expect(html).toMatch("FAKE STACK TRACE");
expect(html).toMatch("💿 Hey developer 👋");
});

it("does not handle render errors for non-data routers", async () => {
Expand Down Expand Up @@ -2280,44 +2192,11 @@ describe("createMemoryRouter", () => {

router.navigate("/child");
await waitFor(() => screen.getByText("Kaboom!"));
expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<div>
<h2>
Unexpected Application Error!
</h2>
<h3
style="font-style: italic;"
>
Kaboom!
</h3>
<pre
style="padding: 0.5rem; background-color: rgba(200, 200, 200, 0.5);"
>
FAKE STACK TRACE
</pre>
<p>
💿 Hey developer 👋
</p>
<p>
You can provide a way better UX than this when your app throws errors by providing your own
<code
style="padding: 2px 4px; background-color: rgba(200, 200, 200, 0.5);"
>
ErrorBoundary
</code>
or
<code
style="padding: 2px 4px; background-color: rgba(200, 200, 200, 0.5);"
>
errorElement
</code>
prop on your route.
</p>
</div>
</div>"
`);
let html = getHtml(container);
expect(html).toMatch("Unexpected Application Error!");
expect(html).toMatch("Kaboom!");
expect(html).toMatch("FAKE STACK TRACE");
expect(html).toMatch("💿 Hey developer 👋");

router.navigate(-1);
await waitFor(() => {
Expand Down Expand Up @@ -2508,6 +2387,120 @@ describe("createMemoryRouter", () => {
);
errorSpy.mockRestore();
});

it("allows a successful useRevalidator to resolve the error boundary (loader + child boundary)", async () => {
let shouldFail = true;
let router = createMemoryRouter(
createRoutesFromElements(
<Route
path="/"
Component={() => (
<>
<MemoryNavigate to="child">/child</MemoryNavigate>
<Outlet />
</>
)}
>
<Route
path="child"
loader={() => {
if (shouldFail) {
shouldFail = false;
throw new Error("Broken");
} else {
return "Fixed";
}
}}
Component={() => <p>{("Child:" + useLoaderData()) as string}</p>}
ErrorBoundary={() => {
let { revalidate } = useRevalidator();
return (
<>
<p>{"Error:" + (useRouteError() as Error).message}</p>
<button onClick={() => revalidate()}>Try again</button>
</>
);
}}
/>
</Route>
)
);

let { container } = render(
<div>
<RouterProvider router={router} />
</div>
);

fireEvent.click(screen.getByText("/child"));
await waitFor(() => screen.getByText("Error:Broken"));
expect(getHtml(container)).toMatch("Error:Broken");
expect(router.state.errors).not.toBe(null);

fireEvent.click(screen.getByText("Try again"));
await waitFor(() => {
expect(queryByText(container, "Child:Fixed")).toBeInTheDocument();
});
expect(getHtml(container)).toMatch("Child:Fixed");
expect(router.state.errors).toBe(null);
});

it("allows a successful useRevalidator to resolve the error boundary (loader + parent boundary)", async () => {
let shouldFail = true;
let router = createMemoryRouter(
createRoutesFromElements(
<Route
path="/"
Component={() => (
<>
<MemoryNavigate to="child">/child</MemoryNavigate>
<Outlet />
</>
)}
ErrorBoundary={() => {
let { revalidate } = useRevalidator();
return (
<>
<p>{"Error:" + (useRouteError() as Error).message}</p>
<button onClick={() => revalidate()}>Try again</button>
</>
);
}}
>
<Route
path="child"
loader={() => {
if (shouldFail) {
shouldFail = false;
throw new Error("Broken");
} else {
return "Fixed";
}
}}
Component={() => <p>{("Child:" + useLoaderData()) as string}</p>}
/>
</Route>
)
);

let { container } = render(
<div>
<RouterProvider router={router} />
</div>
);

fireEvent.click(screen.getByText("/child"));
await waitFor(() => screen.getByText("Error:Broken"));
expect(getHtml(container)).toMatch("Error:Broken");
expect(router.state.errors).not.toBe(null);

fireEvent.click(screen.getByText("Try again"));
await waitFor(() => {
expect(queryByText(container, "Child:Fixed")).toBeInTheDocument();
});
expect(getHtml(container)).toMatch("Child:Fixed");
expect(router.state.errors).toBe(null);
});
});

describe("defer", () => {
Expand Down

0 comments on commit fda0123

Please sign in to comment.