facebook/react · error · Error
Cannot requestFormReset() inside a startGestureTransition. T
Error message
Cannot requestFormReset() inside a startGestureTransition. There should be no side-effects associated with starting a Gesture until its Action is invoked. Move side-effects to the Action instead.
What it means
requestFormReset is the internal behind resetting a React-controlled form. Inside a gesture transition the current transition object carries gesture, and the scope callback of startGestureTransition runs in that context. React forbids resetting a form there: starting a Gesture must be free of side effects — DOM state changes belong to the Action the gesture eventually invokes, not to starting it.
Source
Thrown at packages/react-reconciler/src/ReactFiberHooks.js:3397
if (transition === null) {
if (__DEV__) {
// An optimistic update occurred, but startTransition is not on the stack.
// The form reset will be scheduled at default (sync) priority, which
// is probably not what the user intended. Most likely because the
// requestFormReset call happened after an `await`.
// TODO: Theoretically, requestFormReset is still useful even for
// non-transition updates because it allows you to update defaultValue
// synchronously and then wait to reset until after the update commits.
// I've chosen to warn anyway because it's more likely the `await` mistake
// described above. But arguably we shouldn't.
console.error(
'requestFormReset was called outside a transition or action. To ' +
'fix, move to an action, or wrap with startTransition.',
);
}
} else if (enableGestureTransition && transition.gesture) {
throw new Error(
'Cannot requestFormReset() inside a startGestureTransition. ' +
'There should be no side-effects associated with starting a ' +
'Gesture until its Action is invoked. Move side-effects to the ' +
'Action instead.',
);
}
let stateHook: Hook = ensureFormComponentIsStateful(formFiber);
const newResetState = {};
if (stateHook.next === null) {
// Hack alert. If formFiber is the workInProgress Fiber then
// we might get a broken intermediate state. Try the alternate
// instead.
// TODO: We should really stash the Queue somewhere stateful
// just like how setState binds the Queue.
stateHook = (formFiber.alternate as any).memoizedState;
}
const resetStateHook: Hook = stateHook.next as any;View on GitHub (pinned to eafeac097b)
Solutions
- Move the requestFormReset/form.reset() call into the Action the gesture invokes (run it when the action executes, not when the gesture starts)
- If the reset must precede the gesture, use startTransition or an action instead of a gesture transition
- Keep the startGestureTransition scope callback side-effect free (schedule reads, updates, and DOM mutations elsewhere)
Example fix
// before
startGestureTransition(timeline, () => {
formRef.current.reset(); // throws: side effect at Gesture start
});
// after — run the reset inside the Action the Gesture invokes
startGestureTransition(timeline, () => {
scheduleAction(() => {
formRef.current.reset(); // side effects allowed inside the Action
});
}); Defensive patterns
Strategy: validation
Validate before calling
// Keep gesture scope callbacks pure: assert no DOM side effects sneak in (dev)
function pureScope(fn: () => void): () => void {
return () => {
if (document.activeElement === null) { /* heuristic */ }
fn();
};
} Try / catch
// The throw is routed through reportGlobalError by startGestureTransition, so catch it at the root:
createRoot(container, {
onUncaughtError(error) {
if (String(error).includes('requestFormReset')) {
telemetry.count('form-reset-inside-gesture');
}
},
}).render(<App />); Prevention
- Treat the startGestureTransition scope callback as pure: schedule updates, do not touch the DOM
- Put form resets and other side effects inside the Action the gesture invokes
- If a side effect must run before the gesture, use startTransition or an action instead
- Write a lint rule or review checklist: no formRef.current.reset() inside gesture callbacks
When it happens
Trigger: Calling requestFormReset (directly or via formRef.current.reset() on a React-controlled form) inside the synchronous scope callback passed to unstable_startGestureTransition / startGestureTransition(timeline, () => { ... }). The throw happens while transition.gesture is set.
Common situations: Porting startTransition habits (where synchronous side effects in the callback are tolerated) to the experimental gesture API; optimistically clearing inputs the moment a gesture begins; exploratory work on experimental gesture/timeline builds.
Related errors
AI-assisted analysis of facebook/react@eafeac097b (2026-08-21).
Data as JSON: /api/errors/e5616dd03dd49d11.
Report an issue: GitHub.