nextjs - 💡(How to fix) Fix State reset during useLayoutEffect context update in React 19 + Next.js 16 [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#89107Fetched 2026-04-08 02:03:21
View on GitHub
Comments
1
Participants
2
Timeline
4
Reactions
0
Timeline (top)
closed ×1commented ×1labeled ×1locked ×1

Root Cause

The issue appears related to React 19's concurrent rendering:

  1. setIsEditing(true) triggers a state update
  2. Component re-renders with isEditing: true
  3. useLayoutEffect runs synchronously and calls context.setContent(...)
  4. Context state update triggers a re-render cascade
  5. During this cascade, the component's isEditing state appears to reset to its previous value

Note: The context reference is stabilized with useCallback to prevent infinite loops - the bug still occurs.

Fix Action

Fix / Workaround

Workarounds

Code Example

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.3.0

Binaries:
  Node: 22.x
  npm: 10.x

Relevant Packages:
  next: 16.1.1-canary.4
  react: 19.0.0
  react-dom: 19.0.0

Turbopack: Enabled

---

git clone https://github.com/jpoindexter/nextjs-context-state-reset-repro.git
cd nextjs-context-state-reset-repro
npm install
npm run dev
# Open http://localhost:3000
# Open browser console (F12)
# Click the "EDIT" button
# Watch console logs show state reset

---

Click EDIT → isEditing becomes trueEdit form appears

---

Click EDIT → isEditing briefly becomes trueState resets to falseView mode persists

---

[Editor] Render #1, isEditing: false
[Editor] startEditing called, current isEditing: false
[Editor] Render #2, isEditing: true    // ✓ Good
[Editor] Render #3, isEditing: false   // ✗ BAD - state reset!

---

// Before (buggy)
useLayoutEffect(() => {
  context?.setContent(content);
  return () => context?.setContent(null);
}, [context, content, updateKey]);

// After (works)
useEffect(() => {
  context?.setContent(content);
  return () => context?.setContent(null);
}, [context, content, updateKey]);

---

const startEditing = useCallback(() => {
  localStorage.setItem('editing-flag', 'true');
  window.location.reload(); // Force fresh mount
}, []);

// In useState initializer:
const [isEditing] = useState(() => {
  if (typeof window === 'undefined') return false;
  const flag = localStorage.getItem('editing-flag');
  if (flag === 'true') {
    localStorage.removeItem('editing-flag');
    return true;
  }
  return false;
});
RAW_BUFFERClick to expand / collapse

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.3.0

Binaries:
  Node: 22.x
  npm: 10.x

Relevant Packages:
  next: 16.1.1-canary.4
  react: 19.0.0
  react-dom: 19.0.0

Turbopack: Enabled

Which area(s) are affected?

  • App Router
  • Turbopack

Link to the code that reproduces this issue

🔗 https://github.com/jpoindexter/nextjs-context-state-reset-repro

git clone https://github.com/jpoindexter/nextjs-context-state-reset-repro.git
cd nextjs-context-state-reset-repro
npm install
npm run dev
# Open http://localhost:3000
# Open browser console (F12)
# Click the "EDIT" button
# Watch console logs show state reset

Describe the Bug

When a component uses useLayoutEffect to update React Context state, and then triggers a local state change, the local state appears to "reset" during the render cascade in React 19.

Scenario

We have an in-situ editing pattern:

  1. Editor component has local state isEditing
  2. Custom hook (useSetRightRail) uses useLayoutEffect to push content to a context
  3. When user clicks "EDIT", setIsEditing(true) is called
  4. The useLayoutEffect updates the context with edit-mode content

Expected Behavior

Click EDIT → isEditing becomes true → Edit form appears

Actual Behavior

Click EDIT → isEditing briefly becomes true → State resets to false → View mode persists

The context updates correctly (right rail shows edit mode), but the component's local state reverts.

Console Output

[Editor] Render #1, isEditing: false
[Editor] startEditing called, current isEditing: false
[Editor] Render #2, isEditing: true    // ✓ Good
[Editor] Render #3, isEditing: false   // ✗ BAD - state reset!

Root Cause Analysis

The issue appears related to React 19's concurrent rendering:

  1. setIsEditing(true) triggers a state update
  2. Component re-renders with isEditing: true
  3. useLayoutEffect runs synchronously and calls context.setContent(...)
  4. Context state update triggers a re-render cascade
  5. During this cascade, the component's isEditing state appears to reset to its previous value

Note: The context reference is stabilized with useCallback to prevent infinite loops - the bug still occurs.

Why Page Refresh Works

After clicking EDIT (which sets a localStorage flag), refreshing the page works because:

  1. Component mounts fresh with no previous state
  2. useState initializer reads localStorage and returns true
  3. No state transition occurs - component starts in edit mode
  4. useLayoutEffect runs but doesn't interfere with an already-settled state

Workarounds

1. Use useEffect instead of useLayoutEffect

// Before (buggy)
useLayoutEffect(() => {
  context?.setContent(content);
  return () => context?.setContent(null);
}, [context, content, updateKey]);

// After (works)
useEffect(() => {
  context?.setContent(content);
  return () => context?.setContent(null);
}, [context, content, updateKey]);

2. Page Reload Strategy (our production fix)

const startEditing = useCallback(() => {
  localStorage.setItem('editing-flag', 'true');
  window.location.reload(); // Force fresh mount
}, []);

// In useState initializer:
const [isEditing] = useState(() => {
  if (typeof window === 'undefined') return false;
  const flag = localStorage.getItem('editing-flag');
  if (flag === 'true') {
    localStorage.removeItem('editing-flag');
    return true;
  }
  return false;
});

Key Files in Reproduction

  • src/contexts/right-rail-context.tsx - Context with useLayoutEffect hook (bug trigger)
  • src/components/editor.tsx - Component demonstrating the state reset
  • src/components/right-rail.tsx - Shows context IS updating correctly

Additional Context

  • This bug does not occur in React 18
  • This bug occurs with Turbopack enabled
  • The issue is specific to useLayoutEffect triggering context state updates during a state transition
  • We discovered this while building an in-situ editing feature with Claude Code
  • Related to #89050 (closed for missing reproduction)

This reproduction was created while debugging production code with Claude Code.

extent analysis

TL;DR

The most likely fix for the state reset issue is to replace useLayoutEffect with useEffect in the useSetRightRail custom hook.

Guidance

  • Verify that the issue is indeed caused by the concurrent rendering in React 19 by checking the console output and the component's render cascade.
  • Try replacing useLayoutEffect with useEffect in the useSetRightRail custom hook to see if it resolves the issue.
  • If the above fix does not work, consider implementing a page reload strategy using localStorage to force a fresh mount of the component.
  • Review the src/contexts/right-rail-context.tsx and src/components/editor.tsx files to ensure that the context and component state are being updated correctly.

Example

// Before (buggy)
useLayoutEffect(() => {
  context?.setContent(content);
  return () => context?.setContent(null);
}, [context, content, updateKey]);

// After (works)
useEffect(() => {
  context?.setContent(content);
  return () => context?.setContent(null);
}, [context, content, updateKey]);

Notes

  • This fix assumes that the issue is specific to the useLayoutEffect hook and its interaction with the context state update.
  • The page reload strategy using localStorage may not be desirable in all cases, and alternative solutions should be explored.

Recommendation

Apply the workaround by replacing useLayoutEffect with useEffect in the useSetRightRail custom hook, as it is a more straightforward and less invasive fix compared to the page reload strategy.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING