Skip to content

Commit 0379d51

Browse files
authored
Respect preventScrollReset on fetcher.Form (#9963)
* Respect preventScrollReset on fetcher.Form * Add preventScrollReset to form docs
1 parent db73e94 commit 0379d51

File tree

5 files changed

+87
-2
lines changed

5 files changed

+87
-2
lines changed

.changeset/chilly-stingrays-rhyme.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@remix-run/router": patch
3+
"react-router-dom": patch
4+
---
5+
6+
Respect `preventScrollReset` on `fetcher.Form`

docs/components/form.md

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

218+
## `preventScrollReset`
219+
220+
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.
221+
222+
```tsx
223+
<Form method="post" preventScrollReset={true} />
224+
```
225+
226+
See also: [`<Link preventScrollReset>`][link-preventscrollreset]
227+
218228
# Examples
219229

220230
TODO: More examples
@@ -318,3 +328,5 @@ You can access those values from the `request.url`
318328
[formvalidation]: https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation
319329
[indexsearchparam]: ../guides/index-search-param
320330
[pickingarouter]: ../routers/picking-a-router
331+
[scrollrestoration]: ./scroll-restoration
332+
[link-preventscrollreset]: ./link#preventscrollreset

docs/components/scroll-restoration.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,14 @@ Or you may want to only use the pathname for some paths, and use the normal beha
8383

8484
## Preventing Scroll Reset
8585

86-
When navigation creates new scroll keys, the scroll position is reset to the top of the page. You can prevent the "scroll to top" behavior from your links:
86+
When navigation creates new scroll keys, the scroll position is reset to the top of the page. You can prevent the "scroll to top" behavior from your links and forms:
8787

8888
```tsx
8989
<Link preventScrollReset={true} />
90+
<Form preventScrollReset={true} />
9091
```
9192

92-
See also: [`<Link preventScrollReset>`][preventscrollreset]
93+
See also: [`<Link preventScrollReset>`][preventscrollreset], [`<Form preventScrollReset>`][form-preventscrollreset]
9394

9495
## Scroll Flashing
9596

@@ -99,4 +100,5 @@ Server Rendering frameworks can prevent scroll flashing because they can send a
99100

100101
[remix]: https://remix.run
101102
[preventscrollreset]: ../components/link#preventscrollreset
103+
[form-preventscrollreset]: ../components/form#preventscrollreset
102104
[pickingarouter]: ../routers/picking-a-router

packages/router/__tests__/router-test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6702,6 +6702,37 @@ describe("a router", () => {
67026702
expect(t.router.state.restoreScrollPosition).toBe(null);
67036703
expect(t.router.state.preventScrollReset).toBe(false);
67046704
});
6705+
6706+
it("resets on fetch submissions that redirect", async () => {
6707+
let t = setup({
6708+
routes: SCROLL_ROUTES,
6709+
initialEntries: ["/tasks"],
6710+
hydrationData: {
6711+
loaderData: {
6712+
tasks: "TASKS",
6713+
},
6714+
},
6715+
});
6716+
6717+
expect(t.router.state.restoreScrollPosition).toBe(false);
6718+
expect(t.router.state.preventScrollReset).toBe(false);
6719+
6720+
let positions = {};
6721+
let activeScrollPosition = 0;
6722+
t.router.enableScrollRestoration(
6723+
positions,
6724+
() => activeScrollPosition
6725+
);
6726+
6727+
let nav1 = await t.fetch("/tasks", {
6728+
formMethod: "post",
6729+
formData: createFormData({}),
6730+
});
6731+
let nav2 = await nav1.actions.tasks.redirectReturn("/tasks");
6732+
await nav2.loaders.tasks.resolve("TASKS 2");
6733+
expect(t.router.state.restoreScrollPosition).toBe(null);
6734+
expect(t.router.state.preventScrollReset).toBe(false);
6735+
});
67056736
});
67066737

67076738
describe("user-specified flag preventScrollReset flag", () => {
@@ -6823,6 +6854,38 @@ describe("a router", () => {
68236854
expect(t.router.state.restoreScrollPosition).toBe(null);
68246855
expect(t.router.state.preventScrollReset).toBe(true);
68256856
});
6857+
6858+
it("prevents scroll reset on fetch submissions that redirect", async () => {
6859+
let t = setup({
6860+
routes: SCROLL_ROUTES,
6861+
initialEntries: ["/tasks"],
6862+
hydrationData: {
6863+
loaderData: {
6864+
tasks: "TASKS",
6865+
},
6866+
},
6867+
});
6868+
6869+
expect(t.router.state.restoreScrollPosition).toBe(false);
6870+
expect(t.router.state.preventScrollReset).toBe(false);
6871+
6872+
let positions = {};
6873+
let activeScrollPosition = 0;
6874+
t.router.enableScrollRestoration(
6875+
positions,
6876+
() => activeScrollPosition
6877+
);
6878+
6879+
let nav1 = await t.fetch("/tasks", {
6880+
formMethod: "post",
6881+
formData: createFormData({}),
6882+
preventScrollReset: true,
6883+
});
6884+
let nav2 = await nav1.actions.tasks.redirectReturn("/tasks");
6885+
await nav2.loaders.tasks.resolve("TASKS 2");
6886+
expect(t.router.state.restoreScrollPosition).toBe(null);
6887+
expect(t.router.state.preventScrollReset).toBe(true);
6888+
});
68266889
});
68276890
});
68286891
});

packages/router/router.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,6 +1507,8 @@ export function createRouter(init: RouterInit): Router {
15071507
let { path, submission } = normalizeNavigateOptions(href, opts, true);
15081508
let match = getTargetMatch(matches, path);
15091509

1510+
pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
1511+
15101512
if (submission && isMutationMethod(submission.formMethod)) {
15111513
handleFetcherAction(key, routeId, path, match, matches, submission);
15121514
return;

0 commit comments

Comments
 (0)