Skip to content

Commit c0881a9

Browse files
fix: prevent fetcher formData clearing before loaderData updates (#14506)
1 parent c2cb601 commit c0881a9

File tree

1 file changed

+31
-2
lines changed

1 file changed

+31
-2
lines changed

packages/react-router/lib/router/router.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,6 +1363,19 @@ export function createRouter(init: RouterInit): Router {
13631363
)
13641364
: state.loaderData;
13651365

1366+
// Transition any fetchers that were kept in loading state (with formData) to idle
1367+
// now that we're committing the loaderData. This ensures the fetcher state change
1368+
// and loaderData update happen atomically in the same updateState() call.
1369+
let fetchers = newState.fetchers ? new Map(newState.fetchers) : new Map(state.fetchers);
1370+
let updatedFetchers = false;
1371+
fetchers.forEach((fetcher, key) => {
1372+
if (fetcher.state === "loading" && fetcher.formData) {
1373+
// Transition to idle now that loaderData is being committed
1374+
fetchers.set(key, getDoneFetcher(fetcher.data));
1375+
updatedFetchers = true;
1376+
}
1377+
});
1378+
13661379
// On a successful navigation we can assume we got through all blockers
13671380
// so we can start fresh
13681381
let blockers = state.blockers;
@@ -1436,7 +1449,7 @@ export function createRouter(init: RouterInit): Router {
14361449

14371450
updateState(
14381451
{
1439-
...newState, // matches, errors, fetchers go through as-is
1452+
...newState, // matches, errors go through as-is
14401453
actionData,
14411454
loaderData,
14421455
historyAction: pendingAction,
@@ -1447,6 +1460,8 @@ export function createRouter(init: RouterInit): Router {
14471460
restoreScrollPosition,
14481461
preventScrollReset,
14491462
blockers,
1463+
// Use updated fetchers if we transitioned any from loading to idle
1464+
...(updatedFetchers ? { fetchers } : {}),
14501465
},
14511466
{
14521467
viewTransitionOpts,
@@ -6549,8 +6564,22 @@ function processLoaderData(
65496564
// keep this to type narrow to a success result in the else
65506565
invariant(false, "Unhandled fetcher revalidation redirect");
65516566
} else {
6567+
// Get the current fetcher state to check if it has formData
6568+
let existingFetcher = state.fetchers.get(key);
65526569
let doneFetcher = getDoneFetcher(result.data);
6553-
state.fetchers.set(key, doneFetcher);
6570+
6571+
// If the fetcher currently has formData, keep it
6572+
// in loading state with the new data until completeNavigation commits both
6573+
// the fetcher state and loaderData together. This prevents a flicker where
6574+
// fetcher.formData becomes undefined before new loaderData is available.
6575+
if (existingFetcher && existingFetcher.formData) {
6576+
state.fetchers.set(key, {
6577+
...existingFetcher,
6578+
data: result.data,
6579+
});
6580+
} else {
6581+
state.fetchers.set(key, doneFetcher);
6582+
}
65546583
}
65556584
});
65566585

0 commit comments

Comments
 (0)