Skip to content

Commit

Permalink
Add support for replace() redirects (#11811)
Browse files Browse the repository at this point in the history
Co-authored-by: Brendan Allan <brendonovich@outlook.com>
  • Loading branch information
brophdawg11 and Brendonovich authored Jul 18, 2024
1 parent bc5b15d commit 01d0f41
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 5 deletions.
7 changes: 7 additions & 0 deletions .changeset/five-bottles-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@remix-run/router": minor
"react-router": minor
"react-router-dom": minor
---

Add a new `replace(url, init?)` alternative to `redirect(url, init?)` that performs a `history.replaceState` instead of a `history.pushState` on client-side navigation redirects
1 change: 1 addition & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
- bhbs
- bilalk711
- bobziroll
- Brendonovich
- BrianT1414
- brockross
- brookslybrand
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@
"none": "14.9 kB"
},
"packages/react-router/dist/umd/react-router.production.min.js": {
"none": "17.3 kB"
"none": "17.4 kB"
},
"packages/react-router-dom/dist/react-router-dom.production.min.js": {
"none": "17.3 kB"
},
"packages/react-router-dom/dist/umd/react-router-dom.production.min.js": {
"none": "23.6 kB"
"none": "23.7 kB"
}
},
"pnpm": {
Expand Down
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 @@ -154,6 +154,7 @@ export {
parsePath,
redirect,
redirectDocument,
replace,
renderMatches,
resolvePath,
unstable_HistoryRouter,
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 @@ -178,6 +178,7 @@ export {
parsePath,
redirect,
redirectDocument,
replace,
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 @@ -97,6 +97,7 @@ export {
parsePath,
redirect,
redirectDocument,
replace,
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 @@ -49,6 +49,7 @@ import {
parsePath,
redirect,
redirectDocument,
replace,
resolvePath,
UNSAFE_warning as warning,
} from "@remix-run/router";
Expand Down Expand Up @@ -206,6 +207,7 @@ export {
parsePath,
redirect,
redirectDocument,
replace,
renderMatches,
resolvePath,
useBlocker,
Expand Down
74 changes: 72 additions & 2 deletions packages/router/__tests__/redirects-test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { IDLE_NAVIGATION } from "../index";
import {
IDLE_NAVIGATION,
createBrowserHistory,
createMemoryHistory,
createRouter,
} from "../index";
import { replace } from "../utils";
import type { TestRouteObject } from "./utils/data-router-setup";
import { cleanup, setup } from "./utils/data-router-setup";
import { createFormData } from "./utils/utils";
import { createFormData, tick } from "./utils/utils";

describe("redirects", () => {
afterEach(() => cleanup());
Expand Down Expand Up @@ -642,6 +648,70 @@ describe("redirects", () => {
});
});

it("supports replace() redirects", async () => {
let router = createRouter({
history: createMemoryHistory(),
routes: [
{
path: "/",
},
{
path: "/a",
},
{
path: "/b",
loader: () => replace("/c"),
},
{
path: "/c",
},
],
});
router.initialize();
await tick();

// ['/']
expect(router.state).toMatchObject({
historyAction: "POP",
location: {
pathname: "/",
state: null,
},
});

// Push /a: ['/', '/a']
await router.navigate("/a");
expect(router.state).toMatchObject({
historyAction: "PUSH",
location: {
pathname: "/a",
state: null,
},
});

// Push /b which calls replace('/c'): ['/', '/c']
await router.navigate("/b");
expect(router.state).toMatchObject({
historyAction: "REPLACE",
location: {
pathname: "/c",
state: {
_isRedirect: true,
},
},
});

// Pop: ['/']
await router.navigate(-1);
expect(router.state).toMatchObject({
historyAction: "POP",
location: {
pathname: "/",
state: null,
},
});
});

describe("redirect status code handling", () => {
it("should not treat 300 as a redirect", 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 @@ -48,6 +48,7 @@ export {
normalizePathname,
redirect,
redirectDocument,
replace,
resolvePath,
resolveTo,
stripBasename,
Expand Down
4 changes: 3 additions & 1 deletion packages/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2683,7 +2683,9 @@ export function createRouter(init: RouterInit): Router {
pendingNavigationController = null;

let redirectHistoryAction =
replace === true ? HistoryAction.Replace : HistoryAction.Push;
replace === true || redirect.response.headers.has("X-Remix-Replace")
? HistoryAction.Replace
: HistoryAction.Push;

// Use the incoming submission if provided, fallback on the active one in
// state.navigation
Expand Down
12 changes: 12 additions & 0 deletions packages/router/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1619,6 +1619,18 @@ export const redirectDocument: RedirectFunction = (url, init) => {
return response;
};

/**
* A redirect response that will perform a `history.replaceState` instead of a
* `history.pushState` for client-side navigation redirects.
* Sets the status code and the `Location` header.
* Defaults to "302 Found".
*/
export const replace: RedirectFunction = (url, init) => {
let response = redirect(url, init);
response.headers.set("X-Remix-Replace", "true");
return response;
};

export type ErrorResponse = {
status: number;
statusText: string;
Expand Down

0 comments on commit 01d0f41

Please sign in to comment.