{"record":{"id":"90c7120b49a63c2f","repo":"facebook/react","slug":"cannot-setstate-on-regular-state-inside-a-startges","errorCode":null,"errorMessage":"Cannot setState on regular state inside a startGestureTransition. Gestures can only update the useOptimistic() hook. There should be no side-effects associated with starting a Gesture until its Action is invoked. Move side-effects to the Action instead.","messagePattern":"Cannot setState on regular state inside a startGestureTransition\\. Gestures can only update the useOptimistic\\(\\) hook\\. There should be no side-effects associated with starting a Gesture until its Action is invoked\\. Move side-effects to the Action instead\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/react-reconciler/src/ReactFiberWorkLoop.js","lineNumber":837,"sourceCode":"    workInProgressRootRenderLanes !== NoLanes\n  ) {\n    // This is a render phase update. These are not officially supported. The\n    // old behavior is to give this the same \"thread\" (lanes) as\n    // whatever is currently rendering. So if you call `setState` on a component\n    // that happens later in the same render, it will flush. Ideally, we want to\n    // remove the special case and treat them as if they came from an\n    // interleaved event. Regardless, this pattern is not officially supported.\n    // This behavior is only a fallback. The flag only exists until we can roll\n    // out the setState warning, since existing code might accidentally rely on\n    // the current behavior.\n    return pickArbitraryLane(workInProgressRootRenderLanes);\n  }\n\n  const transition = requestCurrentTransition();\n  if (transition !== null) {\n    if (enableGestureTransition) {\n      if (transition.gesture) {\n        throw new Error(\n          'Cannot setState on regular state inside a startGestureTransition. ' +\n            'Gestures can only update the useOptimistic() hook. There should be no ' +\n            'side-effects associated with starting a Gesture until its Action is ' +\n            'invoked. Move side-effects to the Action instead.',\n        );\n      }\n    }\n    if (__DEV__) {\n      if (!transition._updatedFibers) {\n        transition._updatedFibers = new Set();\n      }\n      transition._updatedFibers.add(fiber);\n      if (\n        hasPotentialUseWarnings() &&\n        resolveUpdatePriority() === DiscreteEventPriority\n      ) {\n        // If we're updating inside a discrete event, then this might be a new user interaction\n        // and not just an automatically resolved loading sequence. Don't warn unless it happens again.","sourceCodeStart":819,"sourceCodeEnd":855,"githubUrl":"https://github.com/facebook/react/blob/eafeac097ba51e1eab809c07102126bd5f8e5425/packages/react-reconciler/src/ReactFiberWorkLoop.js#L819-L855","documentation":"startGestureTransition(provider, scope) marks updates scheduled inside scope with a transition carrying a gesture flag, and the gesture contract allows only useOptimistic state to change during the gesture phase. requestUpdateLane inspects the current transition for every update; when it has a gesture flag and the update is a regular setState, React throws immediately, because gesture-phase side effects would break the optimistic-update and view-transition model.","triggerScenarios":"Calling any non-optimistic state update — a useState setter, useReducer dispatch, context provider write, or external store update that schedules a render — synchronously inside the startGestureTransition scope callback, before the gesture's Action runs.","commonSituations":"Wiring drag/press gestures where the handler also toggles unrelated UI state (selection, hover, menus) directly in the scope; migrating code from startTransition where side effects were tolerated; experimental builds with enableGestureTransition enabled.","solutions":["Keep only useOptimistic updates inside the startGestureTransition scope — the gesture-driven value must come from the useOptimistic hook","Move every side effect out of the scope callback and into the Action that runs when the gesture commits","If unrelated state must change when the gesture starts, update it before calling startGestureTransition or in the Action, never inside the scope"],"exampleFix":"// before\nunstable_startGestureTransition(timeline, () => {\n  setSelected(true); // regular useState setter — throws\n  setOptimisticOffset(dx); // useOptimistic setter — allowed\n});\n\n// after\nunstable_startGestureTransition(timeline, () => {\n  setOptimisticOffset(dx); // useOptimistic only\n});\n// side effects happen in the Action invoked when the gesture commits\nfunction commitGesture() {\n  setSelected(true);\n  startTransition(() => setOffset(dragContext.current));\n}","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep the startGestureTransition scope callback free of every setter except useOptimistic ones","Put side effects in the Action that runs when the gesture commits, never in the start scope","Keep the scope synchronous — an async scope is rejected separately"],"tags":["gesture-transition","use-optimistic","set-state","transitions","experimental"],"backgroundTag":"setstate-in-gesture-transition","analyzedSha":"eafeac097ba51e1eab809c07102126bd5f8e5425","analyzedAt":"2026-08-21T22:01:08.818Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}