Skip to content

Commit bbc7715

Browse files
authored
Add missing <Form state> prop (#10630)
1 parent 499af9a commit bbc7715

File tree

7 files changed

+100
-17
lines changed

7 files changed

+100
-17
lines changed

.changeset/form-state-prop.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router-dom": patch
3+
---
4+
5+
Add missing `<Form state>` prop to populate `history.state` on submission navigations

docs/components/form.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,24 @@ See also:
215215
- [`useActionData`][useactiondata]
216216
- [`useSubmit`][usesubmit]
217217

218+
## `state`
219+
220+
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()`.
221+
222+
```tsx
223+
<Form
224+
method="post"
225+
action="new-path"
226+
state={{ some: "value" }}
227+
/>
228+
```
229+
230+
You can access this state value while on the "new-path" route:
231+
232+
```ts
233+
let { state } = useLocation();
234+
```
235+
218236
## `preventScrollReset`
219237

220238
If you are using [`<ScrollRestoration>`][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`
330348
[pickingarouter]: ../routers/picking-a-router
331349
[scrollrestoration]: ./scroll-restoration
332350
[link-preventscrollreset]: ./link#preventscrollreset
351+
[history-state]: https://developer.mozilla.org/en-US/docs/Web/API/History/state

docs/hooks/use-submit.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,7 @@ submit(null, {
150150
<Form action="/logout" method="post" />;
151151
```
152152

153+
Because submissions are navigations, the options may also contain the other navigation related props from [`<Form>`][form] such as `replace`, `state`, `preventScrollReset`, `relative`, etc.
154+
153155
[pickingarouter]: ../routers/picking-a-router
156+
[form]: ../components/form.md

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
"none": "16.2 kB"
119119
},
120120
"packages/react-router-dom/dist/react-router-dom.production.min.js": {
121-
"none": "12.7 kB"
121+
"none": "12.8 kB"
122122
},
123123
"packages/react-router-dom/dist/umd/react-router-dom.production.min.js": {
124124
"none": "18.7 kB"

packages/react-router-dom/__tests__/data-browser-router-test.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,6 +1584,46 @@ function testDomRouter(
15841584
`);
15851585
});
15861586

1587+
it("supports <Form state>", async () => {
1588+
let testWindow = getWindow("/");
1589+
let router = createTestRouter(
1590+
[
1591+
{
1592+
path: "/",
1593+
Component() {
1594+
return (
1595+
<Form method="post" action="/action" state={{ key: "value" }}>
1596+
<button type="submit">Submit</button>
1597+
</Form>
1598+
);
1599+
},
1600+
},
1601+
{
1602+
path: "/action",
1603+
action: () => null,
1604+
Component() {
1605+
let state = useLocation().state;
1606+
return <p>{JSON.stringify(state)}</p>;
1607+
},
1608+
},
1609+
],
1610+
{ window: testWindow }
1611+
);
1612+
let { container } = render(<RouterProvider router={router} />);
1613+
expect(testWindow.history.state.usr).toBeUndefined();
1614+
1615+
fireEvent.click(screen.getByText("Submit"));
1616+
await waitFor(() => screen.getByText('{"key":"value"}'));
1617+
expect(getHtml(container)).toMatchInlineSnapshot(`
1618+
"<div>
1619+
<p>
1620+
{"key":"value"}
1621+
</p>
1622+
</div>"
1623+
`);
1624+
expect(testWindow.history.state.usr).toEqual({ key: "value" });
1625+
});
1626+
15871627
it("supports <Form reloadDocument={true}>", async () => {
15881628
let actionSpy = jest.fn();
15891629
let router = createTestRouter(

packages/react-router-dom/dom.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ export interface SubmitOptions {
171171
*/
172172
replace?: boolean;
173173

174+
/**
175+
* State object to add to the history stack entry for this navigation
176+
*/
177+
state?: any;
178+
174179
/**
175180
* Determines whether the form action is relative to the route hierarchy or
176181
* the pathname. Use this if you want to opt out of navigating the route

packages/react-router-dom/index.tsx

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,8 @@ if (__DEV__) {
716716
NavLink.displayName = "NavLink";
717717
}
718718

719-
export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
719+
export interface FetcherFormProps
720+
extends React.FormHTMLAttributes<HTMLFormElement> {
720721
/**
721722
* The HTTP verb to use when the form is submit. Supports "get", "post",
722723
* "put", "delete", "patch".
@@ -737,18 +738,6 @@ export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
737738
*/
738739
action?: string;
739740

740-
/**
741-
* Forces a full document navigation instead of a fetch.
742-
*/
743-
reloadDocument?: boolean;
744-
745-
/**
746-
* Replaces the current entry in the browser history stack when the form
747-
* navigates. Use this if you don't want the user to be able to click "back"
748-
* to the page with the form on it.
749-
*/
750-
replace?: boolean;
751-
752741
/**
753742
* Determines whether the form action is relative to the route hierarchy or
754743
* the pathname. Use this if you want to opt out of navigating the route
@@ -769,6 +758,25 @@ export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
769758
onSubmit?: React.FormEventHandler<HTMLFormElement>;
770759
}
771760

761+
export interface FormProps extends FetcherFormProps {
762+
/**
763+
* Forces a full document navigation instead of a fetch.
764+
*/
765+
reloadDocument?: boolean;
766+
767+
/**
768+
* Replaces the current entry in the browser history stack when the form
769+
* navigates. Use this if you don't want the user to be able to click "back"
770+
* to the page with the form on it.
771+
*/
772+
replace?: boolean;
773+
774+
/**
775+
* State object to add to the history stack entry for this navigation
776+
*/
777+
state?: any;
778+
}
779+
772780
/**
773781
* A `@remix-run/router`-aware `<form>`. It behaves like a normal form except
774782
* that the interaction with the server is with `fetch` instead of new document
@@ -803,6 +811,7 @@ const FormImpl = React.forwardRef<HTMLFormElement, FormImplProps>(
803811
{
804812
reloadDocument,
805813
replace,
814+
state,
806815
method = defaultMethod,
807816
action,
808817
onSubmit,
@@ -831,6 +840,7 @@ const FormImpl = React.forwardRef<HTMLFormElement, FormImplProps>(
831840
submit(submitter || event.currentTarget, {
832841
method: submitMethod,
833842
replace,
843+
state,
834844
relative,
835845
preventScrollReset,
836846
});
@@ -1048,8 +1058,8 @@ export interface SubmitFunction {
10481058
export interface FetcherSubmitFunction {
10491059
(
10501060
target: SubmitTarget,
1051-
// Fetchers cannot replace because they are not navigation events
1052-
options?: Omit<SubmitOptions, "replace">
1061+
// Fetchers cannot replace or set state because they are not navigation events
1062+
options?: Omit<SubmitOptions, "replace" | "state">
10531063
): void;
10541064
}
10551065

@@ -1087,6 +1097,7 @@ export function useSubmit(): SubmitFunction {
10871097
formMethod: options.method || (method as HTMLFormMethod),
10881098
formEncType: options.encType || (encType as FormEncType),
10891099
replace: options.replace,
1100+
state: options.state,
10901101
fromRouteId: currentRouteId,
10911102
});
10921103
},
@@ -1186,7 +1197,7 @@ export function useFormAction(
11861197
}
11871198

11881199
function createFetcherForm(fetcherKey: string, routeId: string) {
1189-
let FetcherForm = React.forwardRef<HTMLFormElement, FormProps>(
1200+
let FetcherForm = React.forwardRef<HTMLFormElement, FetcherFormProps>(
11901201
(props, ref) => {
11911202
let submit = useSubmitFetcher(fetcherKey, routeId);
11921203
return <FormImpl {...props} ref={ref} submit={submit} />;

0 commit comments

Comments
 (0)