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

Add support to force reload on redirect with X-Remix-Reload-Document header #10705

Merged
merged 14 commits into from
Aug 2, 2023
1 change: 1 addition & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,4 @@
- smithki
- istarkov
- louis-young
- robbtraister
1 change: 1 addition & 0 deletions packages/react-router-dom-v5-compat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export {
Form,
json,
redirect,
redirectWithReload,
useActionData,
useAsyncError,
useAsyncValue,
Expand Down
1 change: 1 addition & 0 deletions packages/react-router-dom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export {
matchRoutes,
parsePath,
redirect,
redirectWithReload,
renderMatches,
resolvePath,
useActionData,
Expand Down
1 change: 1 addition & 0 deletions packages/react-router-native/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export {
matchRoutes,
parsePath,
redirect,
redirectWithReload,
renderMatches,
resolvePath,
useActionData,
Expand Down
2 changes: 2 additions & 0 deletions packages/react-router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
matchRoutes,
parsePath,
redirect,
redirectWithReload,
resolvePath,
UNSAFE_warning as warning,
} from "@remix-run/router";
Expand Down Expand Up @@ -187,6 +188,7 @@ export {
matchRoutes,
parsePath,
redirect,
redirectWithReload,
renderMatches,
resolvePath,
useActionData,
Expand Down
39 changes: 39 additions & 0 deletions packages/router/__tests__/router-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7004,6 +7004,45 @@ describe("a router", () => {
}
});

it("processes redirects with document reload if header is present (assign)", async () => {
let urls = ["/redirect"];

for (let url of urls) {
brophdawg11 marked this conversation as resolved.
Show resolved Hide resolved
let t = setup({ routes: REDIRECT_ROUTES });

let A = await t.navigate("/parent/child", {
formMethod: "post",
formData: createFormData({}),
});

await A.actions.child.redirectReturn(url, 301, {
"X-Remix-Reload-Document": "true",
});
expect(t.window.location.assign).toHaveBeenCalledWith(url);
expect(t.window.location.replace).not.toHaveBeenCalled();
}
});

it("processes redirects with document reload if header is present (replace)", async () => {
let urls = ["/redirect"];

for (let url of urls) {
let t = setup({ routes: REDIRECT_ROUTES });

let A = await t.navigate("/parent/child", {
formMethod: "post",
formData: createFormData({}),
replace: true,
});

await A.actions.child.redirectReturn(url, 301, {
"X-Remix-Reload-Document": "true",
});
expect(t.window.location.replace).toHaveBeenCalledWith(url);
expect(t.window.location.assign).not.toHaveBeenCalled();
}
});

it("properly handles same-origin absolute URLs", async () => {
let t = setup({ routes: REDIRECT_ROUTES });

Expand Down
1 change: 1 addition & 0 deletions packages/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export {
matchRoutes,
normalizePathname,
redirect,
redirectWithReload,
resolvePath,
resolveTo,
stripBasename,
Expand Down
38 changes: 26 additions & 12 deletions packages/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2110,19 +2110,32 @@ export function createRouter(init: RouterInit): Router {
redirectLocation,
"Expected a location on the redirect navigation"
);
// Check if this an absolute external redirect that goes to a new origin
if (ABSOLUTE_URL_REGEX.test(redirect.location) && isBrowser) {
let url = init.history.createURL(redirect.location);
let isDifferentBasename = stripBasename(url.pathname, basename) == null;

if (routerWindow.location.origin !== url.origin || isDifferentBasename) {
if (replace) {
routerWindow.location.replace(redirect.location);
} else {
routerWindow.location.assign(redirect.location);
}
return;

// Check if this redirect should trigger a document reload
const isDocumentReload = (thisOrigin: string) => {
if (redirect.reloadDocument) {
return true;
}
if (!ABSOLUTE_URL_REGEX.test(redirect.location)) {
return false;
}

const url = init.history.createURL(redirect.location);
// Check if this an absolute external redirect that goes to a new origin
if (thisOrigin !== url.origin) {
return true;
}

const isDifferentBasename = stripBasename(url.pathname, basename) == null;
return isDifferentBasename;
};
if (isBrowser && isDocumentReload(routerWindow.location.origin)) {
if (replace) {
routerWindow.location.replace(redirect.location);
} else {
routerWindow.location.assign(redirect.location);
}
return;
}

// There's no need to abort on redirects, since we don't detect the
Expand Down Expand Up @@ -3733,6 +3746,7 @@ async function callLoaderOrAction(
status,
location,
revalidate: result.headers.get("X-Remix-Revalidate") !== null,
reloadDocument: result.headers.get("X-Remix-Reload-Document") !== null,
};
}

Expand Down
18 changes: 18 additions & 0 deletions packages/router/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface RedirectResult {
status: number;
location: string;
revalidate: boolean;
reloadDocument?: boolean;
}

/**
Expand Down Expand Up @@ -1484,6 +1485,23 @@ export const redirect: RedirectFunction = (url, init = 302) => {
});
};

/**
* A redirect response with a forced document reload. Sets a custom header to
* trigger the client-side reload.
* Defaults to "302 Found".
*/
export const redirectWithReload: RedirectFunction = (url, init = 302) => {
let responseInit = init;
if (typeof responseInit === "number") {
responseInit = { status: responseInit };
}

responseInit.headers = new Headers(responseInit.headers);
responseInit.headers.set("X-Remix-Reload-Document", "true");

return redirect(url, responseInit);
};
brophdawg11 marked this conversation as resolved.
Show resolved Hide resolved

/**
* @private
* Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
Expand Down