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 missing <Form state> prop #10630

Merged
merged 6 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/form-state-prop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-router-dom": patch
---

Add missing `<Form state>` prop to populate `history.state` on submission navigations
19 changes: 19 additions & 0 deletions docs/components/form.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<Form
method="post"
action="new-path"
state={{ some: "value" }}
/>
```

You can access this state value while on the "new-path" route:

```ts
let { state } = useLocation();
```

## `preventScrollReset`

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.
Expand Down Expand Up @@ -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
3 changes: 3 additions & 0 deletions docs/hooks/use-submit.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,7 @@ submit(null, {
<Form action="/logout" method="post" />;
```

Because submissions are navigations, the options may also contain the other navigation related props from [`<Form>`][form] such as `replace`, `state`, `preventScrollReset`, `relative`, etc.

[pickingarouter]: ../routers/picking-a-router
[form]: ../components/form.md
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
"none": "16.2 kB"
},
"packages/react-router-dom/dist/react-router-dom.production.min.js": {
"none": "12.5 kB"
"none": "12.6 kB"
},
"packages/react-router-dom/dist/umd/react-router-dom.production.min.js": {
"none": "18.6 kB"
Expand Down
40 changes: 40 additions & 0 deletions packages/react-router-dom/__tests__/data-browser-router-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1541,6 +1541,46 @@ function testDomRouter(
`);
});

it("supports <Form state>", async () => {
let testWindow = getWindow("/");
let router = createTestRouter(
[
{
path: "/",
Component() {
return (
<Form method="post" action="/action" state={{ key: "value" }}>
<button type="submit">Submit</button>
</Form>
);
},
},
{
path: "/action",
action: () => null,
Component() {
let state = useLocation().state;
return <p>{JSON.stringify(state)}</p>;
},
},
],
{ window: testWindow }
);
let { container } = render(<RouterProvider router={router} />);
expect(testWindow.history.state.usr).toBeUndefined();

fireEvent.click(screen.getByText("Submit"));
await waitFor(() => screen.getByText('{"key":"value"}'));
expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<p>
{"key":"value"}
</p>
</div>"
`);
expect(testWindow.history.state.usr).toEqual({ key: "value" });
});

it("supports <Form reloadDocument={true}>", async () => {
let actionSpy = jest.fn();
let router = createTestRouter(
Expand Down
5 changes: 5 additions & 0 deletions packages/react-router-dom/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,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
Expand Down
8 changes: 8 additions & 0 deletions packages/react-router-dom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,11 @@ export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
*/
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
Expand Down Expand Up @@ -784,6 +789,7 @@ const FormImpl = React.forwardRef<HTMLFormElement, FormImplProps>(
{
reloadDocument,
replace,
state,
method = defaultMethod,
action,
onSubmit,
Expand Down Expand Up @@ -812,6 +818,7 @@ const FormImpl = React.forwardRef<HTMLFormElement, FormImplProps>(
submit(submitter || event.currentTarget, {
method: submitMethod,
replace,
state,
relative,
preventScrollReset,
});
Expand Down Expand Up @@ -1068,6 +1075,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,
});
},
Expand Down