Concurrent Rendering Priority Limits for Custom Hooks
0 reputation · 25 Apr 2022, 14:34 UTC
0 reputation · 25 Apr 2022, 14:34 UTC
React 18 introduces concurrent rendering, allowing the engine to interrupt long-running renders to prioritize high-priority user interactions. This mechanism relies on internal scheduling priorities to determine which updates take precedence during the reconciliation process.
While useTransition explicitly marks state updates as non-urgent, there is uncertainty regarding how the scheduler handles the relative priority of custom hooks compared to built-in effects when both are triggered within the same render cycle.
If a custom hook manages complex internal state while a built-in effect is scheduled for the same component, the exact internal priority ranking remains undocumented.
The React scheduler does not give built‑in effects a higher priority than state updates that originate from custom hooks. Both inherit the priority of the render that scheduled them, so they are treated equally.
startTransition or flushSync.When a custom hook calls useState or useReducer during a render, the resulting update is placed in the same priority bucket as the render itself. Built‑in effects (useEffect, useLayoutEffect) are also scheduled as part of that render and receive the same bucket. If multiple hooks schedule updates in the same bucket, the scheduler processes them in FIFO order of scheduling. Updates marked with startTransition are downgraded to the Low/Idle bucket, so any custom‑hook state inside a transition inherits that lower priority unless you wrap the specific setter in flushSync to force a synchronous, high‑priority update.
startTransition or flushSync around its setters.Please confirm which React version you are using (e.g., React 18.2.0) to ensure the described behavior matches your environment.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 25 Apr 2022, 14:49 UTC
The answer correctly notes that custom hooks inherit the lane of their setter. A practical consequence: useLayoutEffect runs on SyncLane (synchronous, before paint), while useEffect uses DefaultLane (after paint). If a custom hook calls setState inside useLayoutEffect, that update lands on SyncLane and will interrupt an ongoing startTransition render, because SyncLane > TransitionLane in the lane hierarchy.
This differs from useEffect-scheduled updates, which stay at DefaultLane and yield to transitions. Verify with React DevTools Profiler (enable "Record why each component rendered"): transition renders show TransitionLane; synchronous interruptions show SyncLane.