Skip to content

Commit 6ac05b4

Browse files
authored
API Follow Ups (#9059)
* Treat non-false shouldRevalidate as truthy * change return type of useRouteError to unknown * rename Deferred fallback prop to fallbackElement * fix: add reloadDocument to <Form> * Fallback on defaultShouldRevalidate for non-boolean return * Change hook return types from any to unknown
1 parent 9e2f92a commit 6ac05b4

File tree

11 files changed

+269
-30
lines changed

11 files changed

+269
-30
lines changed

.changeset/light-months-argue.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function DeferredPage() {
2727
<p>Critical Data: {data.critical}</p>
2828
<Deferred
2929
value={data.lazy}
30-
fallback={<p>Loading...</p>}
30+
fallbackElement={<p>Loading...</p>}
3131
errorElement={<RenderDeferredError />}>
3232
<RenderDeferredData />
3333
</Deferred>
@@ -57,7 +57,7 @@ function DeferredPage() {
5757
return (
5858
<>
5959
<p>Critical Data: {data.critical}</p>
60-
<Deferred value={data.lazy} fallback={<p>Loading...</p>}>
60+
<Deferred value={data.lazy} fallbackElement={<p>Loading...</p>}>
6161
{(data) => <p>{data}</p>}
6262
</Deferred>
6363
</>

examples/data-router/src/App.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,21 +270,24 @@ function DeferredPage() {
270270
<div>
271271
<p>{data.critical1}</p>
272272
<p>{data.critical2}</p>
273-
<Deferred value={data.lazyResolved} fallback={<p>should not see me!</p>}>
273+
<Deferred
274+
value={data.lazyResolved}
275+
fallbackElement={<p>should not see me!</p>}
276+
>
274277
<RenderDeferredData />
275278
</Deferred>
276-
<Deferred value={data.lazy1} fallback={<p>loading 1...</p>}>
279+
<Deferred value={data.lazy1} fallbackElement={<p>loading 1...</p>}>
277280
<RenderDeferredData />
278281
</Deferred>
279-
<Deferred value={data.lazy2} fallback={<p>loading 2...</p>}>
282+
<Deferred value={data.lazy2} fallbackElement={<p>loading 2...</p>}>
280283
<RenderDeferredData />
281284
</Deferred>
282-
<Deferred value={data.lazy3} fallback={<p>loading 3...</p>}>
285+
<Deferred value={data.lazy3} fallbackElement={<p>loading 3...</p>}>
283286
{(data) => <p>{data}</p>}
284287
</Deferred>
285288
<Deferred
286289
value={data.lazyError}
287-
fallback={<p>loading (error)...</p>}
290+
fallbackElement={<p>loading (error)...</p>}
288291
errorElement={<RenderDeferredError />}
289292
>
290293
<RenderDeferredData />
@@ -311,7 +314,7 @@ function DeferredChild() {
311314
return (
312315
<div>
313316
<p>{data.critical}</p>
314-
<Deferred value={data.lazy} fallback={<p>loading child...</p>}>
317+
<Deferred value={data.lazy} fallbackElement={<p>loading child...</p>}>
315318
<RenderDeferredData />
316319
</Deferred>
317320
<Form method="post">

packages/react-router-dom/__tests__/DataBrowserRouter-test.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,39 @@ function testDomRouter(
769769
`);
770770
});
771771

772+
it("supports <Form reloadDocument={true}>", async () => {
773+
let actionSpy = jest.fn();
774+
render(
775+
<TestDataRouter window={getWindow("/")} hydrationData={{}}>
776+
<Route path="/" action={actionSpy} element={<Home />} />
777+
</TestDataRouter>
778+
);
779+
780+
let handlerCalled;
781+
let defaultPrevented;
782+
783+
function Home() {
784+
return (
785+
<Form
786+
method="post"
787+
reloadDocument={true}
788+
onSubmit={(e) => {
789+
handlerCalled = true;
790+
defaultPrevented = e.defaultPrevented;
791+
}}
792+
>
793+
<input name="test" value="value" />
794+
<button type="submit">Submit Form</button>
795+
</Form>
796+
);
797+
}
798+
799+
fireEvent.click(screen.getByText("Submit Form"));
800+
expect(handlerCalled).toBe(true);
801+
expect(defaultPrevented).toBe(false);
802+
expect(actionSpy).not.toHaveBeenCalled();
803+
});
804+
772805
it('defaults <Form method="get"> to be a PUSH navigation', async () => {
773806
let { container } = render(
774807
<TestDataRouter window={getWindow("/")} hydrationData={{}}>

packages/react-router-dom/__tests__/link-click-test.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,48 @@ describe("A <Link> click", () => {
9797

9898
expect(event.defaultPrevented).toBe(false);
9999
});
100+
101+
it("calls provided listener", () => {
102+
let handlerCalled;
103+
let defaultPrevented;
104+
105+
function Home() {
106+
return (
107+
<div>
108+
<h1>Home</h1>
109+
<Link
110+
reloadDocument
111+
to="../about"
112+
onClick={(e) => {
113+
handlerCalled = true;
114+
defaultPrevented = e.defaultPrevented;
115+
}}
116+
>
117+
About
118+
</Link>
119+
</div>
120+
);
121+
}
122+
123+
act(() => {
124+
ReactDOM.render(
125+
<MemoryRouter initialEntries={["/home"]}>
126+
<Routes>
127+
<Route path="home" element={<Home />} />
128+
<Route path="about" element={<h1>About</h1>} />
129+
</Routes>
130+
</MemoryRouter>,
131+
node
132+
);
133+
});
134+
135+
act(() => {
136+
click(node.querySelector("a"));
137+
});
138+
139+
expect(handlerCalled).toBe(true);
140+
expect(defaultPrevented).toBe(false);
141+
});
100142
});
101143

102144
describe("when preventDefault is used on the click handler", () => {

packages/react-router-dom/index.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
380380
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>
381381
) {
382382
if (onClick) onClick(event);
383-
if (!event.defaultPrevented && !reloadDocument) {
383+
if (!event.defaultPrevented) {
384384
internalOnClick(event);
385385
}
386386
}
@@ -390,7 +390,7 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
390390
<a
391391
{...rest}
392392
href={href}
393-
onClick={handleClick}
393+
onClick={reloadDocument ? onClick : handleClick}
394394
ref={ref}
395395
target={target}
396396
/>
@@ -518,6 +518,11 @@ export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
518518
*/
519519
action?: string;
520520

521+
/**
522+
* Forces a full document navigation instead of a fetch.
523+
*/
524+
reloadDocument?: boolean;
525+
521526
/**
522527
* Replaces the current entry in the browser history stack when the form
523528
* navigates. Use this if you don't want the user to be able to click "back"
@@ -564,6 +569,7 @@ interface FormImplProps extends FormProps {
564569
const FormImpl = React.forwardRef<HTMLFormElement, FormImplProps>(
565570
(
566571
{
572+
reloadDocument,
567573
replace,
568574
method = defaultMethod,
569575
action = ".",
@@ -594,7 +600,7 @@ const FormImpl = React.forwardRef<HTMLFormElement, FormImplProps>(
594600
ref={forwardedRef}
595601
method={formMethod}
596602
action={formAction}
597-
onSubmit={submitHandler}
603+
onSubmit={reloadDocument ? onSubmit : submitHandler}
598604
{...props}
599605
/>
600606
);

packages/react-router/CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
<p>Critical Data: {data.critical}</p>
2929
<Deferred
3030
value={data.lazy}
31-
fallback={<p>Loading...</p>}
31+
fallbackElement={<p>Loading...</p>}
3232
errorElement={<RenderDeferredError />}>
3333
<RenderDeferredData />
3434
</Deferred>
@@ -58,7 +58,7 @@
5858
return (
5959
<>
6060
<p>Critical Data: {data.critical}</p>
61-
<Deferred value={data.lazy} fallback={<p>Loading...</p>}>
61+
<Deferred value={data.lazy} fallbackElement={<p>Loading...</p>}>
6262
{(data) => <p>{data}</p>}
6363
</Deferred>
6464
</>

packages/react-router/__tests__/DataMemoryRouter-test.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1924,7 +1924,7 @@ describe("<DataMemoryRouter>", () => {
19241924
return (
19251925
<>
19261926
<p>{data.critical}</p>
1927-
<Deferred value={data.lazy} fallback={<p>Loading...</p>}>
1927+
<Deferred value={data.lazy} fallbackElement={<p>Loading...</p>}>
19281928
<LazyData />
19291929
</Deferred>
19301930
</>
@@ -2041,7 +2041,10 @@ describe("<DataMemoryRouter>", () => {
20412041
return (
20422042
<>
20432043
<p>{data.critical}</p>
2044-
<Deferred<string> value={data.lazy} fallback={<p>Loading...</p>}>
2044+
<Deferred<string>
2045+
value={data.lazy}
2046+
fallbackElement={<p>Loading...</p>}
2047+
>
20452048
{(data) => <p>{data}</p>}
20462049
</Deferred>
20472050
</>
@@ -2156,7 +2159,7 @@ describe("<DataMemoryRouter>", () => {
21562159
<p>{data.critical}</p>
21572160
<Deferred
21582161
value={data.lazy}
2159-
fallback={<p>Loading...</p>}
2162+
fallbackElement={<p>Loading...</p>}
21602163
errorElement={<LazyError />}
21612164
>
21622165
<LazyData />
@@ -2281,7 +2284,7 @@ describe("<DataMemoryRouter>", () => {
22812284
return (
22822285
<>
22832286
<p>{data.critical}</p>
2284-
<Deferred value={data.lazy} fallback={<p>Loading...</p>}>
2287+
<Deferred value={data.lazy} fallbackElement={<p>Loading...</p>}>
22852288
<LazyData />
22862289
</Deferred>
22872290
</>

packages/react-router/lib/components.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -434,10 +434,10 @@ export interface DeferredResolveRenderFunction<Data> {
434434
(data: ResolvedDeferrable<Data>): JSX.Element;
435435
}
436436

437-
export interface DeferredProps<Data>
438-
extends Omit<React.SuspenseProps, "children"> {
437+
export interface DeferredProps<Data> {
439438
children: React.ReactNode | DeferredResolveRenderFunction<Data>;
440439
value: Data;
440+
fallbackElement: React.SuspenseProps["fallback"];
441441
errorElement?: React.ReactNode;
442442
}
443443

@@ -448,12 +448,12 @@ export interface DeferredProps<Data>
448448
export function Deferred<Data = any>({
449449
children,
450450
value,
451-
fallback,
451+
fallbackElement,
452452
errorElement,
453453
}: DeferredProps<Data>) {
454454
return (
455455
<DeferredContext.Provider value={value}>
456-
<React.Suspense fallback={fallback}>
456+
<React.Suspense fallback={fallbackElement}>
457457
<DeferredWrapper errorElement={errorElement}>
458458
{typeof children === "function" ? (
459459
<ResolveDeferred

packages/react-router/lib/hooks.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -428,15 +428,18 @@ function DefaultErrorElement() {
428428
let error = useRouteError();
429429
let message = isRouteErrorResponse(error)
430430
? `${error.status} ${error.statusText}`
431-
: error?.message || JSON.stringify(error);
431+
: error instanceof Error
432+
? error.message
433+
: JSON.stringify(error);
434+
let stack = error instanceof Error ? error.stack : null;
432435
let lightgrey = "rgba(200,200,200, 0.5)";
433436
let preStyles = { padding: "0.5rem", backgroundColor: lightgrey };
434437
let codeStyles = { padding: "2px 4px", backgroundColor: lightgrey };
435438
return (
436439
<>
437440
<h2>Unhandled Thrown Error!</h2>
438441
<h3 style={{ fontStyle: "italic" }}>{message}</h3>
439-
{error?.stack ? <pre style={preStyles}>{error?.stack}</pre> : null}
442+
{stack ? <pre style={preStyles}>{stack}</pre> : null}
440443
<p>💿 Hey developer 👋</p>
441444
<p>
442445
You can provide a way better UX than this when your app throws errors by
@@ -645,8 +648,8 @@ export function useMatches() {
645648
id: match.route.id,
646649
pathname,
647650
params,
648-
data: loaderData[match.route.id],
649-
handle: match.route.handle,
651+
data: loaderData[match.route.id] as unknown,
652+
handle: match.route.handle as unknown,
650653
};
651654
}),
652655
[matches, loaderData]
@@ -656,7 +659,7 @@ export function useMatches() {
656659
/**
657660
* Returns the loader data for the nearest ancestor Route loader
658661
*/
659-
export function useLoaderData() {
662+
export function useLoaderData(): unknown {
660663
let state = useDataRouterState(DataRouterHook.UseLoaderData);
661664

662665
let route = React.useContext(RouteContext);
@@ -674,15 +677,15 @@ export function useLoaderData() {
674677
/**
675678
* Returns the loaderData for the given routeId
676679
*/
677-
export function useRouteLoaderData(routeId: string): any | undefined {
680+
export function useRouteLoaderData(routeId: string): unknown {
678681
let state = useDataRouterState(DataRouterHook.UseRouteLoaderData);
679682
return state.loaderData[routeId];
680683
}
681684

682685
/**
683686
* Returns the action data for the nearest ancestor Route action
684687
*/
685-
export function useActionData() {
688+
export function useActionData(): unknown {
686689
let state = useDataRouterState(DataRouterHook.UseActionData);
687690

688691
let route = React.useContext(RouteContext);
@@ -696,7 +699,7 @@ export function useActionData() {
696699
* error or a render error. This is intended to be called from your
697700
* errorElement to display a proper error message.
698701
*/
699-
export function useRouteError() {
702+
export function useRouteError(): unknown {
700703
let error = React.useContext(RouteErrorContext);
701704
let state = useDataRouterState(DataRouterHook.UseRouteError);
702705
let route = React.useContext(RouteContext);

0 commit comments

Comments
 (0)