From bbc77157eb48c46cd3f8ffeddb6124effdb80c75 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Fri, 30 Jun 2023 16:25:26 -0400 Subject: [PATCH] Add missing
prop (#10630) --- .changeset/form-state-prop.md | 5 +++ docs/components/form.md | 19 ++++++++ docs/hooks/use-submit.md | 3 ++ package.json | 2 +- .../__tests__/data-browser-router-test.tsx | 40 +++++++++++++++++ packages/react-router-dom/dom.ts | 5 +++ packages/react-router-dom/index.tsx | 43 ++++++++++++------- 7 files changed, 100 insertions(+), 17 deletions(-) create mode 100644 .changeset/form-state-prop.md diff --git a/.changeset/form-state-prop.md b/.changeset/form-state-prop.md new file mode 100644 index 0000000000..fdedef6237 --- /dev/null +++ b/.changeset/form-state-prop.md @@ -0,0 +1,5 @@ +--- +"react-router-dom": patch +--- + +Add missing `` prop to populate `history.state` on submission navigations diff --git a/docs/components/form.md b/docs/components/form.md index b91c636c6a..27329eb8fd 100644 --- a/docs/components/form.md +++ b/docs/components/form.md @@ -215,6 +215,24 @@ See also: - [`useActionData`][useactiondata] - [`useSubmit`][usesubmit] +## `state` + +The `state` property can be used to set a stateful value for the new location which is stored inside [history state][history-state]. This value can subsequently be accessed via `useLocation()`. + +```tsx + +``` + +You can access this state value while on the "new-path" route: + +```ts +let { state } = useLocation(); +``` + ## `preventScrollReset` If you are using [``][scrollrestoration], this lets you prevent the scroll position from being reset to the top of the window when the form action redirects to a new location. @@ -330,3 +348,4 @@ You can access those values from the `request.url` [pickingarouter]: ../routers/picking-a-router [scrollrestoration]: ./scroll-restoration [link-preventscrollreset]: ./link#preventscrollreset +[history-state]: https://developer.mozilla.org/en-US/docs/Web/API/History/state diff --git a/docs/hooks/use-submit.md b/docs/hooks/use-submit.md index 5a897e0a3c..f21347d2d2 100644 --- a/docs/hooks/use-submit.md +++ b/docs/hooks/use-submit.md @@ -150,4 +150,7 @@ submit(null, { ; ``` +Because submissions are navigations, the options may also contain the other navigation related props from [``][form] such as `replace`, `state`, `preventScrollReset`, `relative`, etc. + [pickingarouter]: ../routers/picking-a-router +[form]: ../components/form.md diff --git a/package.json b/package.json index 8570395e57..26d65783e7 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "none": "16.2 kB" }, "packages/react-router-dom/dist/react-router-dom.production.min.js": { - "none": "12.7 kB" + "none": "12.8 kB" }, "packages/react-router-dom/dist/umd/react-router-dom.production.min.js": { "none": "18.7 kB" diff --git a/packages/react-router-dom/__tests__/data-browser-router-test.tsx b/packages/react-router-dom/__tests__/data-browser-router-test.tsx index df6b13cf81..9b0e75fa35 100644 --- a/packages/react-router-dom/__tests__/data-browser-router-test.tsx +++ b/packages/react-router-dom/__tests__/data-browser-router-test.tsx @@ -1584,6 +1584,46 @@ function testDomRouter( `); }); + it("supports ", async () => { + let testWindow = getWindow("/"); + let router = createTestRouter( + [ + { + path: "/", + Component() { + return ( + + + + ); + }, + }, + { + path: "/action", + action: () => null, + Component() { + let state = useLocation().state; + return

{JSON.stringify(state)}

; + }, + }, + ], + { window: testWindow } + ); + let { container } = render(); + expect(testWindow.history.state.usr).toBeUndefined(); + + fireEvent.click(screen.getByText("Submit")); + await waitFor(() => screen.getByText('{"key":"value"}')); + expect(getHtml(container)).toMatchInlineSnapshot(` + "
+

+ {"key":"value"} +

+
" + `); + expect(testWindow.history.state.usr).toEqual({ key: "value" }); + }); + it("supports
", async () => { let actionSpy = jest.fn(); let router = createTestRouter( diff --git a/packages/react-router-dom/dom.ts b/packages/react-router-dom/dom.ts index c1b2911122..9f1054ab41 100644 --- a/packages/react-router-dom/dom.ts +++ b/packages/react-router-dom/dom.ts @@ -171,6 +171,11 @@ export interface SubmitOptions { */ replace?: boolean; + /** + * State object to add to the history stack entry for this navigation + */ + state?: any; + /** * Determines whether the form action is relative to the route hierarchy or * the pathname. Use this if you want to opt out of navigating the route diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx index 4de210b97f..e2c1539663 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -716,7 +716,8 @@ if (__DEV__) { NavLink.displayName = "NavLink"; } -export interface FormProps extends React.FormHTMLAttributes { +export interface FetcherFormProps + extends React.FormHTMLAttributes { /** * The HTTP verb to use when the form is submit. Supports "get", "post", * "put", "delete", "patch". @@ -737,18 +738,6 @@ export interface FormProps extends React.FormHTMLAttributes { */ action?: string; - /** - * Forces a full document navigation instead of a fetch. - */ - reloadDocument?: boolean; - - /** - * Replaces the current entry in the browser history stack when the form - * navigates. Use this if you don't want the user to be able to click "back" - * to the page with the form on it. - */ - replace?: boolean; - /** * Determines whether the form action is relative to the route hierarchy or * the pathname. Use this if you want to opt out of navigating the route @@ -769,6 +758,25 @@ export interface FormProps extends React.FormHTMLAttributes { onSubmit?: React.FormEventHandler; } +export interface FormProps extends FetcherFormProps { + /** + * Forces a full document navigation instead of a fetch. + */ + reloadDocument?: boolean; + + /** + * Replaces the current entry in the browser history stack when the form + * navigates. Use this if you don't want the user to be able to click "back" + * to the page with the form on it. + */ + replace?: boolean; + + /** + * State object to add to the history stack entry for this navigation + */ + state?: any; +} + /** * A `@remix-run/router`-aware ``. It behaves like a normal form except * that the interaction with the server is with `fetch` instead of new document @@ -803,6 +811,7 @@ const FormImpl = React.forwardRef( { reloadDocument, replace, + state, method = defaultMethod, action, onSubmit, @@ -831,6 +840,7 @@ const FormImpl = React.forwardRef( submit(submitter || event.currentTarget, { method: submitMethod, replace, + state, relative, preventScrollReset, }); @@ -1048,8 +1058,8 @@ export interface SubmitFunction { export interface FetcherSubmitFunction { ( target: SubmitTarget, - // Fetchers cannot replace because they are not navigation events - options?: Omit + // Fetchers cannot replace or set state because they are not navigation events + options?: Omit ): void; } @@ -1087,6 +1097,7 @@ export function useSubmit(): SubmitFunction { formMethod: options.method || (method as HTMLFormMethod), formEncType: options.encType || (encType as FormEncType), replace: options.replace, + state: options.state, fromRouteId: currentRouteId, }); }, @@ -1186,7 +1197,7 @@ export function useFormAction( } function createFetcherForm(fetcherKey: string, routeId: string) { - let FetcherForm = React.forwardRef( + let FetcherForm = React.forwardRef( (props, ref) => { let submit = useSubmitFetcher(fetcherKey, routeId); return ;