fix(web): guard ChatPane scroll setState to prevent Maximum update depth error (#3187)

The scroll handler in ChatPane called setScrolledFromBottom on every
scroll event. During streaming, programmatic scrollTop assignments
(followLatestIfPinned via rAF + ResizeObserver) emit synchronous scroll
events while React is still committing prior updates, scheduling a
setState per tick. React eventually trips its update-depth guard with
'Maximum update depth exceeded' (issue surfaces in Next.js 16
Turbopack dev consoles).

Use a functional updater that returns prev when the boolean is
unchanged so React skips the re-render entirely. This breaks the
cascade at the call site without touching the auto-follow logic.

Co-authored-by: nicejames <nicejames@gmail.com>
This commit is contained in:
leessju 2026-05-28 17:32:28 +09:00 committed by GitHub
parent d9623da4af
commit ece3d71cdd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -658,7 +658,12 @@ export function ChatPane({
snapshot(target); snapshot(target);
const distance = const distance =
target.scrollHeight - target.scrollTop - target.clientHeight; target.scrollHeight - target.scrollTop - target.clientHeight;
setScrolledFromBottom(distance > 120); // Functional updater bails out when the value is unchanged so a flood
// of scroll events (e.g. programmatic scrollTop + ResizeObserver
// follow-up during streaming) does not schedule a re-render per tick
// and trip React's "Maximum update depth exceeded" guard.
const next = distance > 120;
setScrolledFromBottom((prev) => (prev === next ? prev : next));
pinnedToBottomRef.current = distance < 80; pinnedToBottomRef.current = distance < 80;
} }
el.addEventListener('scroll', onScroll); el.addEventListener('scroll', onScroll);