Skip to content

Commit bd1135f

Browse files
committed
Automatically reset forms after action finishes (#28804)
This updates the behavior of form actions to automatically reset the form's uncontrolled inputs after the action finishes. This is a frequent feature request for people using actions and it aligns the behavior of client-side form submissions more closely with MPA form submissions. It has no impact on controlled form inputs. It's the same as if you called `form.reset()` manually, except React handles the timing of when the reset happens, which is tricky/impossible to get exactly right in userspace. The reset shouldn't happen until the UI has updated with the result of the action. So, resetting inside the action is too early. Resetting in `useEffect` is better, but it's later than ideal because any effects that run before it will observe the state of the form before it's been reset. It needs to happen in the mutation phase of the transition. More specifically, after all the DOM mutations caused by the transition have been applied. That way the `defaultValue` of the inputs are updated before the values are reset. The idea is that the `defaultValue` represents the current, canonical value sent by the server. Note: this change has no effect on form submissions that aren't triggered by an action. DiffTrain build for commit 41950d1.
1 parent d71465b commit bd1135f

File tree

10 files changed

+186
-70
lines changed

10 files changed

+186
-70
lines changed

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react-test-renderer/cjs/ReactTestRenderer-dev.js

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<881e78352310ebeea1f473a89441e0c9>>
10+
* @generated SignedSource<<8c677055ecc6af7b5a69924e87081621>>
1111
*/
1212

1313
"use strict";
@@ -515,6 +515,7 @@ if (__DEV__) {
515515
var ScheduleRetry = StoreConsistency;
516516
var ShouldSuspendCommit = Visibility;
517517
var DidDefer = ContentReset;
518+
var FormReset = Snapshot;
518519
var LifecycleEffectMask =
519520
Passive$1 | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit)
520521

@@ -573,7 +574,8 @@ if (__DEV__) {
573574
ContentReset |
574575
Ref |
575576
Hydrating |
576-
Visibility;
577+
Visibility |
578+
FormReset;
577579
var LayoutMask = Update | Callback | Ref | Visibility; // TODO: Split into PassiveMountMask and PassiveUnmountMask
578580

579581
var PassiveMask = Passive$1 | Visibility | ChildDeletion; // Union of tags that don't get reset on clones.
@@ -8335,13 +8337,29 @@ if (__DEV__) {
83358337
var _dispatcher$useState = dispatcher.useState(),
83368338
maybeThenable = _dispatcher$useState[0];
83378339

8340+
var nextState;
8341+
83388342
if (typeof maybeThenable.then === "function") {
83398343
var thenable = maybeThenable;
8340-
return useThenable(thenable);
8344+
nextState = useThenable(thenable);
83418345
} else {
83428346
var status = maybeThenable;
8343-
return status;
8347+
nextState = status;
8348+
} // The "reset state" is an object. If it changes, that means something
8349+
// requested that we reset the form.
8350+
8351+
var _dispatcher$useState2 = dispatcher.useState(),
8352+
nextResetState = _dispatcher$useState2[0];
8353+
8354+
var prevResetState =
8355+
currentHook !== null ? currentHook.memoizedState : null;
8356+
8357+
if (prevResetState !== nextResetState) {
8358+
// Schedule a form reset
8359+
currentlyRenderingFiber$1.flags |= FormReset;
83448360
}
8361+
8362+
return nextState;
83458363
}
83468364
function bailoutHooks(current, workInProgress, lanes) {
83478365
workInProgress.updateQueue = current.updateQueue; // TODO: Don't need to reset the flags here, because they're reset in the
@@ -18802,7 +18820,7 @@ if (__DEV__) {
1880218820
// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.
1880318821

1880418822
var offscreenSubtreeIsHidden = false;
18805-
var offscreenSubtreeWasHidden = false;
18823+
var offscreenSubtreeWasHidden = false; // Used to track if a form needs to be reset at the end of the mutation phase.
1880618824
var PossiblyWeakSet = typeof WeakSet === "function" ? WeakSet : Set;
1880718825
var nextEffect = null; // Used for Profiling builds to track updaters.
1880818826

@@ -20735,6 +20753,19 @@ if (__DEV__) {
2073520753
}
2073620754
}
2073720755
}
20756+
20757+
if (flags & FormReset) {
20758+
{
20759+
if (finishedWork.type !== "form") {
20760+
// Paranoid coding. In case we accidentally start using the
20761+
// FormReset bit for something else.
20762+
error(
20763+
"Unexpected host component type. Expected a form. This is a " +
20764+
"bug in React."
20765+
);
20766+
}
20767+
}
20768+
}
2073820769
}
2073920770

2074020771
return;
@@ -26601,7 +26632,7 @@ if (__DEV__) {
2660126632
return root;
2660226633
}
2660326634

26604-
var ReactVersion = "19.0.0-canary-89546c49";
26635+
var ReactVersion = "19.0.0-canary-dec39fbe";
2660526636

2660626637
/*
2660726638
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react-test-renderer/cjs/ReactTestRenderer-prod.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<5159750d4ca65c393d679d370a71d7cb>>
10+
* @generated SignedSource<<b41ef07452ddc0ccf5f4ca65890007ab>>
1111
*/
1212

1313
"use strict";
@@ -2217,10 +2217,16 @@ function renderWithHooksAgain(workInProgress, Component, props, secondArg) {
22172217
return children;
22182218
}
22192219
function TransitionAwareHostComponent() {
2220-
var maybeThenable = ReactSharedInternals.H.useState()[0];
2221-
return "function" === typeof maybeThenable.then
2222-
? useThenable(maybeThenable)
2223-
: maybeThenable;
2220+
var dispatcher = ReactSharedInternals.H,
2221+
maybeThenable = dispatcher.useState()[0];
2222+
maybeThenable =
2223+
"function" === typeof maybeThenable.then
2224+
? useThenable(maybeThenable)
2225+
: maybeThenable;
2226+
dispatcher = dispatcher.useState()[0];
2227+
(null !== currentHook ? currentHook.memoizedState : null) !== dispatcher &&
2228+
(currentlyRenderingFiber$1.flags |= 1024);
2229+
return maybeThenable;
22242230
}
22252231
function bailoutHooks(current, workInProgress, lanes) {
22262232
workInProgress.updateQueue = current.updateQueue;
@@ -6508,7 +6514,7 @@ function recursivelyTraverseMutationEffects(root$jscomp$0, parentFiber) {
65086514
captureCommitPhaseError(childToDelete, parentFiber, error);
65096515
}
65106516
}
6511-
if (parentFiber.subtreeFlags & 12854)
6517+
if (parentFiber.subtreeFlags & 13878)
65126518
for (parentFiber = parentFiber.child; null !== parentFiber; )
65136519
commitMutationEffectsOnFiber(parentFiber, root$jscomp$0),
65146520
(parentFiber = parentFiber.sibling);
@@ -9124,7 +9130,7 @@ var devToolsConfig$jscomp$inline_1019 = {
91249130
throw Error("TestRenderer does not support findFiberByHostInstance()");
91259131
},
91269132
bundleType: 0,
9127-
version: "19.0.0-canary-df9de31a",
9133+
version: "19.0.0-canary-ffe8e2ea",
91289134
rendererPackageName: "react-test-renderer"
91299135
};
91309136
var internals$jscomp$inline_1238 = {
@@ -9155,7 +9161,7 @@ var internals$jscomp$inline_1238 = {
91559161
scheduleRoot: null,
91569162
setRefreshHandler: null,
91579163
getCurrentFiber: null,
9158-
reconcilerVersion: "19.0.0-canary-df9de31a"
9164+
reconcilerVersion: "19.0.0-canary-ffe8e2ea"
91599165
};
91609166
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
91619167
var hook$jscomp$inline_1239 = __REACT_DEVTOOLS_GLOBAL_HOOK__;

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react-test-renderer/cjs/ReactTestRenderer-profiling.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<21f520424802b9edb8fc0a8c96178e1c>>
10+
* @generated SignedSource<<c75c1cb22c2153409d78bbb0e058c921>>
1111
*/
1212

1313
"use strict";
@@ -2305,10 +2305,16 @@ function renderWithHooksAgain(workInProgress, Component, props, secondArg) {
23052305
return children;
23062306
}
23072307
function TransitionAwareHostComponent() {
2308-
var maybeThenable = ReactSharedInternals.H.useState()[0];
2309-
return "function" === typeof maybeThenable.then
2310-
? useThenable(maybeThenable)
2311-
: maybeThenable;
2308+
var dispatcher = ReactSharedInternals.H,
2309+
maybeThenable = dispatcher.useState()[0];
2310+
maybeThenable =
2311+
"function" === typeof maybeThenable.then
2312+
? useThenable(maybeThenable)
2313+
: maybeThenable;
2314+
dispatcher = dispatcher.useState()[0];
2315+
(null !== currentHook ? currentHook.memoizedState : null) !== dispatcher &&
2316+
(currentlyRenderingFiber$1.flags |= 1024);
2317+
return maybeThenable;
23122318
}
23132319
function bailoutHooks(current, workInProgress, lanes) {
23142320
workInProgress.updateQueue = current.updateQueue;
@@ -6934,7 +6940,7 @@ function recursivelyTraverseMutationEffects(root$jscomp$0, parentFiber) {
69346940
captureCommitPhaseError(childToDelete, parentFiber, error);
69356941
}
69366942
}
6937-
if (parentFiber.subtreeFlags & 12854)
6943+
if (parentFiber.subtreeFlags & 13878)
69386944
for (parentFiber = parentFiber.child; null !== parentFiber; )
69396945
commitMutationEffectsOnFiber(parentFiber, root$jscomp$0),
69406946
(parentFiber = parentFiber.sibling);
@@ -9762,7 +9768,7 @@ var devToolsConfig$jscomp$inline_1082 = {
97629768
throw Error("TestRenderer does not support findFiberByHostInstance()");
97639769
},
97649770
bundleType: 0,
9765-
version: "19.0.0-canary-12ee58d1",
9771+
version: "19.0.0-canary-1a91a1bd",
97669772
rendererPackageName: "react-test-renderer"
97679773
};
97689774
(function (internals) {
@@ -9806,7 +9812,7 @@ var devToolsConfig$jscomp$inline_1082 = {
98069812
scheduleRoot: null,
98079813
setRefreshHandler: null,
98089814
getCurrentFiber: null,
9809-
reconcilerVersion: "19.0.0-canary-12ee58d1"
9815+
reconcilerVersion: "19.0.0-canary-1a91a1bd"
98109816
});
98119817
exports._Scheduler = Scheduler;
98129818
exports.act = act;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ed3c65caf042f75fe2fdc2a5e568a9624c6175fb
1+
41950d14a538aa7411b00b28bcd94ae95a45976e

compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/implementations/ReactFabric-dev.fb.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<a4571cdfd524cd72b685d2a3adeafd2c>>
10+
* @generated SignedSource<<9e85187f59d83f3fcc6ccc4cb04215d8>>
1111
*/
1212

1313
"use strict";
@@ -3055,6 +3055,7 @@ to return true:wantsResponderID| |
30553055
var ScheduleRetry = StoreConsistency;
30563056
var ShouldSuspendCommit = Visibility;
30573057
var DidDefer = ContentReset;
3058+
var FormReset = Snapshot;
30583059
var LifecycleEffectMask =
30593060
Passive$1 | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit)
30603061

@@ -3113,7 +3114,8 @@ to return true:wantsResponderID| |
31133114
ContentReset |
31143115
Ref |
31153116
Hydrating |
3116-
Visibility;
3117+
Visibility |
3118+
FormReset;
31173119
var LayoutMask = Update | Callback | Ref | Visibility; // TODO: Split into PassiveMountMask and PassiveUnmountMask
31183120

31193121
var PassiveMask = Passive$1 | Visibility | ChildDeletion; // Union of tags that don't get reset on clones.
@@ -5031,7 +5033,7 @@ to return true:wantsResponderID| |
50315033
function waitForCommitToBeReady() {
50325034
return null;
50335035
}
5034-
var NotPendingTransition = null; // -------------------
5036+
var NotPendingTransition = null;
50355037
// Microtasks
50365038
// -------------------
50375039

@@ -11815,13 +11817,29 @@ to return true:wantsResponderID| |
1181511817
var _dispatcher$useState = dispatcher.useState(),
1181611818
maybeThenable = _dispatcher$useState[0];
1181711819

11820+
var nextState;
11821+
1181811822
if (typeof maybeThenable.then === "function") {
1181911823
var thenable = maybeThenable;
11820-
return useThenable(thenable);
11824+
nextState = useThenable(thenable);
1182111825
} else {
1182211826
var status = maybeThenable;
11823-
return status;
11827+
nextState = status;
11828+
} // The "reset state" is an object. If it changes, that means something
11829+
// requested that we reset the form.
11830+
11831+
var _dispatcher$useState2 = dispatcher.useState(),
11832+
nextResetState = _dispatcher$useState2[0];
11833+
11834+
var prevResetState =
11835+
currentHook !== null ? currentHook.memoizedState : null;
11836+
11837+
if (prevResetState !== nextResetState) {
11838+
// Schedule a form reset
11839+
currentlyRenderingFiber$1.flags |= FormReset;
1182411840
}
11841+
11842+
return nextState;
1182511843
}
1182611844
function bailoutHooks(current, workInProgress, lanes) {
1182711845
workInProgress.updateQueue = current.updateQueue; // TODO: Don't need to reset the flags here, because they're reset in the
@@ -22636,7 +22654,7 @@ to return true:wantsResponderID| |
2263622654
// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.
2263722655

2263822656
var offscreenSubtreeIsHidden = false;
22639-
var offscreenSubtreeWasHidden = false;
22657+
var offscreenSubtreeWasHidden = false; // Used to track if a form needs to be reset at the end of the mutation phase.
2264022658
var PossiblyWeakSet = typeof WeakSet === "function" ? WeakSet : Set;
2264122659
var nextEffect = null; // Used for Profiling builds to track updaters.
2264222660

@@ -30248,7 +30266,7 @@ to return true:wantsResponderID| |
3024830266
return root;
3024930267
}
3025030268

30251-
var ReactVersion = "19.0.0-canary-704f6d1b";
30269+
var ReactVersion = "19.0.0-canary-665179ad";
3025230270

3025330271
/*
3025430272
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol

compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/implementations/ReactFabric-prod.fb.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<2e33e857cb3eee98d5e57d1509811f8c>>
10+
* @generated SignedSource<<acaefb2daefd1d22c7e94851b8d92a8a>>
1111
*/
1212

1313
"use strict";
@@ -3741,10 +3741,16 @@ function renderWithHooksAgain(workInProgress, Component, props, secondArg) {
37413741
}
37423742
function TransitionAwareHostComponent() {
37433743
if (!enableAsyncActions) throw Error("Not implemented.");
3744-
var maybeThenable = ReactSharedInternals.H.useState()[0];
3745-
return "function" === typeof maybeThenable.then
3746-
? useThenable(maybeThenable)
3747-
: maybeThenable;
3744+
var dispatcher = ReactSharedInternals.H,
3745+
maybeThenable = dispatcher.useState()[0];
3746+
maybeThenable =
3747+
"function" === typeof maybeThenable.then
3748+
? useThenable(maybeThenable)
3749+
: maybeThenable;
3750+
dispatcher = dispatcher.useState()[0];
3751+
(null !== currentHook ? currentHook.memoizedState : null) !== dispatcher &&
3752+
(currentlyRenderingFiber$1.flags |= 1024);
3753+
return maybeThenable;
37483754
}
37493755
function bailoutHooks(current, workInProgress, lanes) {
37503756
workInProgress.updateQueue = current.updateQueue;
@@ -7039,7 +7045,7 @@ function doesRequireClone(current, completedWork) {
70397045
if (null !== current && current.child === completedWork.child) return !1;
70407046
if (0 !== (completedWork.flags & 16)) return !0;
70417047
for (current = completedWork.child; null !== current; ) {
7042-
if (0 !== (current.flags & 12854) || 0 !== (current.subtreeFlags & 12854))
7048+
if (0 !== (current.flags & 13878) || 0 !== (current.subtreeFlags & 13878))
70437049
return !0;
70447050
current = current.sibling;
70457051
}
@@ -8202,7 +8208,7 @@ function recursivelyTraverseMutationEffects(root, parentFiber) {
82028208
captureCommitPhaseError(childToDelete, parentFiber, error);
82038209
}
82048210
}
8205-
if (parentFiber.subtreeFlags & 12854)
8211+
if (parentFiber.subtreeFlags & 13878)
82068212
for (parentFiber = parentFiber.child; null !== parentFiber; )
82078213
commitMutationEffectsOnFiber(parentFiber, root),
82088214
(parentFiber = parentFiber.sibling);
@@ -10577,7 +10583,7 @@ var roots = new Map(),
1057710583
devToolsConfig$jscomp$inline_1099 = {
1057810584
findFiberByHostInstance: getInstanceFromNode,
1057910585
bundleType: 0,
10580-
version: "19.0.0-canary-a19c8da4",
10586+
version: "19.0.0-canary-f39e5a74",
1058110587
rendererPackageName: "react-native-renderer",
1058210588
rendererConfig: {
1058310589
getInspectorDataForInstance: getInspectorDataForInstance,
@@ -10620,7 +10626,7 @@ var internals$jscomp$inline_1366 = {
1062010626
scheduleRoot: null,
1062110627
setRefreshHandler: null,
1062210628
getCurrentFiber: null,
10623-
reconcilerVersion: "19.0.0-canary-a19c8da4"
10629+
reconcilerVersion: "19.0.0-canary-f39e5a74"
1062410630
};
1062510631
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
1062610632
var hook$jscomp$inline_1367 = __REACT_DEVTOOLS_GLOBAL_HOOK__;

0 commit comments

Comments
 (0)