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 future.v3_relativeSplatPath flag #8216

Merged
merged 5 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/relative-splat-path.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@remix-run/dev": minor
"@remix-run/react": minor
"@remix-run/server-runtime": minor
"@remix-run/testing": minor
---

Add a new `future.v3_relativeSplatPath` flag to implement a breaking bug fix to relative routing when inside a splat route. For more information, please see the React Router [`6.21.0` Release Notes](https://github.com/remix-run/react-router/blob/release-next/CHANGELOG.md#futurev7_relativesplatpath) and the [`useResolvedPath` docs](https://remix.run/hooks/use-resolved-path#splat-paths).
3 changes: 3 additions & 0 deletions docs/components/form.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ The URL to submit the form data to.

If `undefined`, this defaults to the closest route in context. If a parent route renders a `<Form>` but the URL matches deeper child routes, the form will post to the parent route. Likewise, a form inside the child route will post to the child route. This differs from a native [`<form>`][form_element] that will always point to the full URL.

<docs-info>Please see the [Splat Paths][relativesplatpath] section on the `useResolvedPath` docs for a note on the behavior of the `future.v3_relativeSplatPath` future flag for relative `<Form action>` behavior within splat routes</docs-info>

### `method`

This determines the [HTTP verb][http_verb] to be used: `DELETE`, `GET`, `PATCH`, `POST`, and `PUT`. The default is `GET`.
Expand Down Expand Up @@ -156,3 +158,4 @@ See also:
[view-transitions]: https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
[document-start-view-transition]: https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition
[use-view-transition-state]: ../hooks/use-view-transition-state
[relativesplatpath]: ../hooks/use-resolved-path#splat-paths
3 changes: 3 additions & 0 deletions docs/components/link.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { Link } from "@remix-run/react";
<Link to="/dashboard">Dashboard</Link>;
```

<docs-info>Please see the [Splat Paths][relativesplatpath] section on the `useResolvedPath` docs for a note on the behavior of the `future.v3_relativeSplatPath` future flag for relative `<Link to>` behavior within splat routes</docs-info>

## Props

### `prefetch`
Expand Down Expand Up @@ -197,3 +199,4 @@ Please note that this API is marked unstable and may be subject to breaking chan
[view-transitions]: https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
[document-start-view-transition]: https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition
[use-view-transition-state]: ../hooks/use-view-transition-state
[relativesplatpath]: ../hooks/use-resolved-path#splat-paths
3 changes: 3 additions & 0 deletions docs/hooks/use-href.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ useHref(to, options)

Optional. The path to append to the resolved URL.

<docs-info>Please see the [Splat Paths][relativesplatpath] section on the `useResolvedPath` docs for a note on the behavior of the `future.v3_relativeSplatPath` future flag for relative `useHref()` behavior within splat routes</docs-info>

### `options`

The only option is `{ relative: "route" | "path"}`, which defines the behavior when resolving relative URLs.
Expand All @@ -35,3 +37,4 @@ The only option is `{ relative: "route" | "path"}`, which defines the behavior w

[anchor_element_href_attribute]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#href
[anchor_element]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link
[relativesplatpath]: ./use-resolved-path#splat-paths
3 changes: 3 additions & 0 deletions docs/hooks/use-navigate.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ navigate("..");
navigate("../other/path");
```

<docs-info>Please see the [Splat Paths][relativesplatpath] section on the `useResolvedPath` docs for a note on the behavior of the `future.v3_relativeSplatPath` future flag for relative `useNavigate()` behavior within splat routes</docs-info>

### `to: To`

You can also pass a `To` value:
Expand Down Expand Up @@ -91,3 +93,4 @@ navigate(".", {
[use-view-transition-state]: ../hooks//use-view-transition-state
[action]: ../route/action
[loader]: ../route/loader
[relativesplatpath]: ./use-resolved-path#splat-paths
22 changes: 22 additions & 0 deletions docs/hooks/use-resolved-path.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,31 @@ function SomeComponent() {

This is useful when building links from relative values and used internally for [`<NavLink>`][nav-link-component].

## Splat Paths

The original logic for React Router's `useResolvedPath` hook behaved differently for splat paths which in hindsight was incorrect/buggy behavior. Please see the [React Router Docs][rr-use-resolved-path-splat] for a longer explanation but this was determined to be a "breaking bug fix" and thus was fixed behind a future flag in React Router and exposed up through Remix's future flags. This will become the default behavior in Remix v3, so it is recommended to update your applications at your convenience to be better prepared for the eventual v3 upgrade.

It should be noted that this is the foundation for all relative routing in Remix, so this applies to the following relative path code flows as well:

- `<Link to>`
- `useNavigate()`
- `useHref()`
- `<Form action>`
- `useSubmit()`
- Relative path `redirect` responses returned from loaders and actions

### Behavior without the flag

When this flag is not enabled, the default behavior is that when resolving relative paths inside of a splat route, the splat portion of the path is ignored. So, within a `routes/dashboard.$.tsx` file, `useResolvedPath(".")` would resolve to `/dashboard` even if the current URL was `/dashboard/teams`.

### Behavior with the flag

When you enable the flag, this "bug" is fixed so that path resolution is consistent across all route types, and `useResolvedPath(".")` always resolves to the current pathname for the contextual route. This includes any dynamic param or splat param values, so within a `routes/dashboard.$.tsx` file, `useResolvedPath(".")` would resolve to `/dashboard/teams` when the current URL was `/dashboard/teams`.

## Additional Resources

- [`resolvePath`][rr-resolve-path]

[nav-link-component]: ../components/nav-link
[rr-resolve-path]: https://reactrouter.com/utils/resolve-path
[rr-use-resolved-path-splat]: https://reactrouter.com/hooks/use-resolved-path#splat-paths
3 changes: 3 additions & 0 deletions docs/hooks/use-submit.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ submit(data, {
});
```

<docs-info>Please see the [Splat Paths][relativesplatpath] section on the `useResolvedPath` docs for a note on the behavior of the `future.v3_relativeSplatPath` future flag for relative `useSubmit()` behavior within splat routes</docs-info>

## Additional Resources

**Discussion**
Expand All @@ -102,3 +104,4 @@ submit(data, {
[start-transition]: https://react.dev/reference/react/startTransition
[view-transitions]: https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
[use-view-transition-state]: ../hooks//use-view-transition-state
[relativesplatpath]: ./use-resolved-path#splat-paths
4 changes: 2 additions & 2 deletions integration/form-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ test.describe("Forms", () => {
await app.goto("/projects/blarg");
let html = await app.getHtml();
let el = getElement(html, `#${SPLAT_ROUTE_NO_ACTION}`);
expect(el.attr("action")).toMatch("/projects/blarg");
expect(el.attr("action")).toMatch("/projects");
});

test("no action resolves to URL including search params", async ({
Expand All @@ -931,7 +931,7 @@ test.describe("Forms", () => {
await app.goto("/projects/blarg?foo=bar");
let html = await app.getHtml();
let el = getElement(html, `#${SPLAT_ROUTE_NO_ACTION}`);
expect(el.attr("action")).toMatch("/projects/blarg?foo=bar");
expect(el.attr("action")).toMatch("/projects?foo=bar");
});

test("absolute action resolves relative to the root route", async ({
Expand Down
1 change: 1 addition & 0 deletions packages/remix-dev/__tests__/readConfig-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe("readConfig", () => {
"entryServerFilePath": Any<String>,
"future": {
"v3_fetcherPersist": false,
"v3_relativeSplatPath": false,
},
"mdx": undefined,
"postcss": true,
Expand Down
2 changes: 2 additions & 0 deletions packages/remix-dev/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Dev = {

interface FutureConfig {
v3_fetcherPersist: boolean;
v3_relativeSplatPath: boolean;
}

type NodeBuiltinsPolyfillOptions = Pick<
Expand Down Expand Up @@ -581,6 +582,7 @@ export async function resolveConfig(
// typings this won't be necessary anymore.
let future: FutureConfig = {
v3_fetcherPersist: appConfig.future?.v3_fetcherPersist === true,
v3_relativeSplatPath: appConfig.future?.v3_relativeSplatPath === true,
};

if (appConfig.future) {
Expand Down
2 changes: 1 addition & 1 deletion packages/remix-dev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"@mdx-js/mdx": "^2.3.0",
"@npmcli/package-json": "^4.0.1",
"@remix-run/node": "2.3.1",
"@remix-run/router": "0.0.0-experimental-35fa15e5",
"@remix-run/router": "1.14.0-pre.0",
"@remix-run/server-runtime": "2.3.1",
"@types/mdx": "^2.0.5",
"@vanilla-extract/integration": "^6.2.0",
Expand Down
1 change: 1 addition & 0 deletions packages/remix-dev/vite/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ export const remixVitePlugin: RemixVitePlugin = (options = {}) => {
relativeAssetsBuildDirectory,
future: {
v3_fetcherPersist: options.future?.v3_fetcherPersist === true,
v3_relativeSplatPath: options.future?.v3_relativeSplatPath === true,
},
};
};
Expand Down
1 change: 1 addition & 0 deletions packages/remix-react/browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ export function RemixBrowser(_props: RemixBrowserProps): ReactElement {
v7_fetcherPersist: window.__remixContext.future.v3_fetcherPersist,
v7_partialHydration: true,
v7_prependBasename: true,
v7_relativeSplatPath: window.__remixContext.future.v3_relativeSplatPath,
},
hydrationData,
mapRouteProperties,
Expand Down
1 change: 1 addition & 0 deletions packages/remix-react/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface EntryContext extends RemixContextObject {

export interface FutureConfig {
v3_fetcherPersist: boolean;
v3_relativeSplatPath: boolean;
}

export interface AssetsManifest {
Expand Down
6 changes: 3 additions & 3 deletions packages/remix-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
"typings": "dist/index.d.ts",
"module": "dist/esm/index.js",
"dependencies": {
"@remix-run/router": "0.0.0-experimental-35fa15e5",
"@remix-run/router": "1.14.0-pre.0",
"@remix-run/server-runtime": "2.3.1",
"react-router": "0.0.0-experimental-35fa15e5",
"react-router-dom": "0.0.0-experimental-35fa15e5"
"react-router": "6.21.0-pre.0",
"react-router-dom": "6.21.0-pre.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.17.0",
Expand Down
1 change: 1 addition & 0 deletions packages/remix-react/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export function RemixServer({
let router = createStaticRouter(routes, context.staticHandlerContext, {
future: {
v7_partialHydration: true,
v7_relativeSplatPath: context.future.v3_relativeSplatPath,
},
});

Expand Down
1 change: 1 addition & 0 deletions packages/remix-server-runtime/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface EntryContext {

export interface FutureConfig {
v3_fetcherPersist: boolean;
v3_relativeSplatPath: boolean;
}

export interface AssetsManifest {
Expand Down
2 changes: 1 addition & 1 deletion packages/remix-server-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"typings": "dist/index.d.ts",
"module": "dist/esm/index.js",
"dependencies": {
"@remix-run/router": "0.0.0-experimental-35fa15e5",
"@remix-run/router": "1.14.0-pre.0",
"@types/cookie": "^0.5.3",
"@web3-storage/multipart-parser": "^1.0.0",
"cookie": "^0.5.0",
Expand Down
6 changes: 5 additions & 1 deletion packages/remix-server-runtime/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ function derive(build: ServerBuild, mode?: string) {
let routes = createRoutes(build.routes);
let dataRoutes = createStaticHandlerDataRoutes(build.routes, build.future);
let serverMode = isServerMode(mode) ? mode : ServerMode.Production;
let staticHandler = createStaticHandler(dataRoutes);
let staticHandler = createStaticHandler(dataRoutes, {
future: {
v7_relativeSplatPath: build.future?.v3_relativeSplatPath,
},
});

let errorHandler =
build.entry.module.handleError ||
Expand Down
1 change: 1 addition & 0 deletions packages/remix-testing/create-remix-stub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export function createRemixStub(
remixContextRef.current = {
future: {
v3_fetcherPersist: future?.v3_fetcherPersist === true,
v3_relativeSplatPath: future?.v3_relativeSplatPath === true,
},
manifest: {
routes: {},
Expand Down
4 changes: 2 additions & 2 deletions packages/remix-testing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"dependencies": {
"@remix-run/node": "2.3.1",
"@remix-run/react": "2.3.1",
"@remix-run/router": "0.0.0-experimental-35fa15e5",
"react-router-dom": "0.0.0-experimental-35fa15e5"
"@remix-run/router": "1.14.0-pre.0",
"react-router-dom": "6.21.0-pre.0"
},
"devDependencies": {
"@types/node": "^18.17.1",
Expand Down
30 changes: 15 additions & 15 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2314,10 +2314,10 @@
"@changesets/types" "^5.0.0"
dotenv "^8.1.0"

"@remix-run/router@0.0.0-experimental-35fa15e5":
version "0.0.0-experimental-35fa15e5"
resolved "https://registry.npmjs.org/@remix-run/router/-/router-0.0.0-experimental-35fa15e5.tgz#f23be11e4d7b7a4260125c70827061882dadb61a"
integrity sha512-pwZ538/KJD18v0OzOPjLrBz1adpJ1I/+ounD1zckjAkBp2Jpm+oQKfzmh/58tryxwMete9Ij7vG0thz5aveAFg==
"@remix-run/router@1.14.0-pre.0":
version "1.14.0-pre.0"
resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.14.0-pre.0.tgz#e19bd909a4dcd6682f08ad62691b6663eea3b0cd"
integrity sha512-AbyTs3orZCJaR1xKPHB7ovMa/kIDeaKwcrYNsXhFlbSB40PHmISmUPiCM7+ZRBZ70yN/ilEgDxhxHO8+w+kZMg==

"@remix-run/web-blob@^3.1.0":
version "3.1.0"
Expand Down Expand Up @@ -11024,20 +11024,20 @@ react-refresh@^0.14.0:
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz"
integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==

react-router-dom@0.0.0-experimental-35fa15e5:
version "0.0.0-experimental-35fa15e5"
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-0.0.0-experimental-35fa15e5.tgz#eb8ff2547c3c047d21f38903083d6d9148a56c90"
integrity sha512-g+82k1K2aeSvWp+TuR/P1pvnxtXRwAJvEULCeX0u63JitO7tT3t2pE8QaRIfrhHAoeubZFwXHcAmlqg8xufsUA==
react-router-dom@6.21.0-pre.0:
version "6.21.0-pre.0"
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.0-pre.0.tgz#d83dce6cd4138f812c56de38a8c8fe5904875f67"
integrity sha512-B0bge8t6MmtUhvCRd8GN49DYXT6xqP0K/Twv2XfXcR3vFFkcnRPs20yTanNvbROljQJJInRgjj0jqWH5BVPfEQ==
dependencies:
"@remix-run/router" "0.0.0-experimental-35fa15e5"
react-router "0.0.0-experimental-35fa15e5"
"@remix-run/router" "1.14.0-pre.0"
react-router "6.21.0-pre.0"

react-router@0.0.0-experimental-35fa15e5:
version "0.0.0-experimental-35fa15e5"
resolved "https://registry.npmjs.org/react-router/-/react-router-0.0.0-experimental-35fa15e5.tgz#81a5e95aa976ce565cb980fc16df90954299b2f5"
integrity sha512-F+IvQVWQWmsTUvpUXBl3FdgyT+GUCJmHmEKeljo7e792UfJ51j5kwSttuAREwuDX4z5D8JRMnF6/grIHp/AT/A==
react-router@6.21.0-pre.0:
version "6.21.0-pre.0"
resolved "https://registry.npmjs.org/react-router/-/react-router-6.21.0-pre.0.tgz#1569190717723cc454078c12a08cebfbb8cb71ef"
integrity sha512-ec9JqPyoGwT3x5lFDbh9RrD7UD+E0ygRvkItIZefnt8y9ilB1ovbSNYApz5FZ4erTvWpvQz22SoZq5fkB9vGVQ==
dependencies:
"@remix-run/router" "0.0.0-experimental-35fa15e5"
"@remix-run/router" "1.14.0-pre.0"

react@^18.2.0:
version "18.2.0"
Expand Down
Loading