99 useEffect ,
1010 useReducer ,
1111 useRef ,
12- useTransition ,
12+ startTransition ,
1313} from 'react' ;
1414import type { ConsoleMessageItem } from '../types.js' ;
1515import {
@@ -71,10 +71,11 @@ export function useConsoleMessages(): UseConsoleMessagesReturn {
7171 const [ consoleMessages , dispatch ] = useReducer ( consoleMessagesReducer , [ ] ) ;
7272 const messageQueueRef = useRef < ConsoleMessageItem [ ] > ( [ ] ) ;
7373 const timeoutRef = useRef < NodeJS . Timeout | null > ( null ) ;
74- const [ , startTransition ] = useTransition ( ) ;
74+ const isProcessingRef = useRef ( false ) ;
7575
7676 const processQueue = useCallback ( ( ) => {
7777 if ( messageQueueRef . current . length > 0 ) {
78+ isProcessingRef . current = true ;
7879 const messagesToProcess = messageQueueRef . current ;
7980 messageQueueRef . current = [ ] ;
8081 startTransition ( ( ) => {
@@ -87,15 +88,26 @@ export function useConsoleMessages(): UseConsoleMessagesReturn {
8788 const handleNewMessage = useCallback (
8889 ( message : ConsoleMessageItem ) => {
8990 messageQueueRef . current . push ( message ) ;
90- if ( ! timeoutRef . current ) {
91- // Batch updates using a timeout. 16ms is a reasonable delay to batch
92- // rapid-fire messages without noticeable lag.
93- timeoutRef . current = setTimeout ( processQueue , 16 ) ;
91+ if ( ! isProcessingRef . current && ! timeoutRef . current ) {
92+ // Batch updates using a timeout. 50ms is a reasonable delay to batch
93+ // rapid-fire messages without noticeable lag while avoiding React update
94+ // queue flooding.
95+ timeoutRef . current = setTimeout ( processQueue , 50 ) ;
9496 }
9597 } ,
9698 [ processQueue ] ,
9799 ) ;
98100
101+ // Once the updated consoleMessages have been committed to the screen,
102+ // we can safely process the next batch of queued messages if any exist.
103+ // This completely eliminates overlapping concurrent updates to this state.
104+ useEffect ( ( ) => {
105+ isProcessingRef . current = false ;
106+ if ( messageQueueRef . current . length > 0 && ! timeoutRef . current ) {
107+ timeoutRef . current = setTimeout ( processQueue , 50 ) ;
108+ }
109+ } , [ consoleMessages , processQueue ] ) ;
110+
99111 useEffect ( ( ) => {
100112 const handleConsoleLog = ( payload : ConsoleLogPayload ) => {
101113 let content = payload . content ;
@@ -149,6 +161,7 @@ export function useConsoleMessages(): UseConsoleMessagesReturn {
149161 timeoutRef . current = null ;
150162 }
151163 messageQueueRef . current = [ ] ;
164+ isProcessingRef . current = true ;
152165 startTransition ( ( ) => {
153166 dispatch ( { type : 'CLEAR' } ) ;
154167 } ) ;
0 commit comments