@@ -172,6 +172,20 @@ function renderGroupRows<G extends TimeTableGroup, I extends TimeSlotBooking>(
172172 )
173173 }
174174 const groupEntriesArray = groupRowsKeys
175+ // Defensive: if the index is beyond available keys, skip rendering instead of throwing
176+ if ( g >= groupEntriesArray . length ) {
177+ if ( timeTableDebugLogs ) {
178+ console . warn (
179+ "TimeTable - group index out of key bounds" ,
180+ g ,
181+ groupEntriesArray . length ,
182+ groupRows ,
183+ changedGroupRowsRef ,
184+ renderCells ,
185+ )
186+ }
187+ return
188+ }
175189 const groupEntry = groupEntriesArray [ g ]
176190 if ( ! groupEntry ) {
177191 console . warn (
@@ -184,7 +198,8 @@ function renderGroupRows<G extends TimeTableGroup, I extends TimeSlotBooking>(
184198 changedGroupRowsRef ,
185199 renderCells ,
186200 )
187- throw new Error ( `TimeTable - group ${ g } entry not found` )
201+ // Do not throw here as this can happen due to async scheduling; skip this render turn
202+ return
188203 }
189204 const nextGroupId = groupEntriesArray [ g + 1 ] ?. id ?? null
190205 const previousGroupId = groupEntriesArray [ g - 1 ] ?. id ?? null
@@ -454,7 +469,11 @@ export default function TimeTableRows<
454469 } , [ intersectionContainerRef . current , headerRef . current , rowHeight ] )
455470
456471 const currentGroupRowsRef = useRef ( groupRows )
457- const groupRowKeys = useMemo ( ( ) => groupRows . keys ( ) . toArray ( ) , [ groupRows ] )
472+ const previousGroupRowsForRetry = useRef ( groupRows )
473+ const groupRowKeys = useMemo (
474+ ( ) => Array . from ( groupRows . keys ( ) ) ,
475+ [ groupRows ] ,
476+ )
458477
459478 const hasViewTypeChanged =
460479 slotsArrayCurrent . current !== slotsArray ||
@@ -542,8 +561,6 @@ export default function TimeTableRows<
542561 }
543562 }
544563
545- currentGroupRowsRef . current = groupRows
546-
547564 if ( timeTableDebugLogs ) {
548565 console . info (
549566 `TimeTable - groupRows changed, marked ${ changedGroupRows . current . size } groups for re-validation` ,
@@ -558,7 +575,12 @@ export default function TimeTableRows<
558575 for ( let i = 0 ; i < groupRowKeys . length ; i ++ ) {
559576 const group = groupRowKeys [ i ]
560577 if ( ! group ) {
561- throw new Error ( `TimeTable - group ${ i } not found` )
578+ console . warn (
579+ `TimeTable - group ${ i } not found in groupRowKeys` ,
580+ i ,
581+ groupRowKeys . length ,
582+ )
583+ continue // Skip this group
562584 }
563585 const rows = groupRows . get ( group )
564586 const currentRows = currentGroupRowsRef . current . get ( group )
@@ -584,6 +606,10 @@ export default function TimeTableRows<
584606 }
585607 }
586608
609+ // Store previous state for retry mechanism before updating
610+ previousGroupRowsForRetry . current = currentGroupRowsRef . current
611+ currentGroupRowsRef . current = groupRows
612+
587613 for ( const changedG of changedGroupRows . current ) {
588614 if ( changedG > groupRowKeys . length - 1 ) {
589615 // delete obsolete change
@@ -639,6 +665,10 @@ export default function TimeTableRows<
639665 const renderBatch = useCallback ( ( ) => {
640666 setGroupRowsRendered ( ( groupRowsRenderedPrev ) => {
641667 const groupRowsRendered = [ ...groupRowsRenderedPrev ]
668+ // Take a consistent snapshot of the current group rows and keys for this batch
669+ const snapshotMap = currentGroupRowsRef . current
670+ const snapshotKeys = Array . from ( snapshotMap . keys ( ) ) as G [ ]
671+ const snapshotSize = snapshotKeys . length
642672 if ( changedGroupRows . current . size ) {
643673 let counter = 0
644674 if ( renderGroupRangeRef . current [ 0 ] > - 1 ) {
@@ -647,14 +677,14 @@ export default function TimeTableRows<
647677 i <= renderGroupRangeRef . current [ 1 ] ;
648678 i ++
649679 ) {
650- if ( i > currentGroupRowsRef . current . size - 1 ) {
680+ if ( i > snapshotSize - 1 ) {
651681 changedGroupRows . current . delete ( i )
652682 continue
653683 }
654684 // make sure visible rows are rendered
655685 if ( changedGroupRows . current . has ( i ) ) {
656- const groupEntryKey = groupRowKeys [ i ]
657- const groupItemRows = groupRows . get ( groupEntryKey )
686+ const groupEntryKey = snapshotKeys [ i ]
687+ const groupItemRows = snapshotMap . get ( groupEntryKey )
658688 if ( ! groupItemRows ) {
659689 if ( timeTableDebugLogs ) {
660690 console . log (
@@ -685,8 +715,8 @@ export default function TimeTableRows<
685715
686716 renderGroupRows (
687717 renderGroupRangeRef . current ,
688- currentGroupRowsRef . current ,
689- groupRowKeys ,
718+ snapshotMap ,
719+ snapshotKeys ,
690720 i ,
691721 refCollection . current ,
692722 groupRowsRendered ,
@@ -714,7 +744,7 @@ export default function TimeTableRows<
714744 }
715745 for ( const g of changedGroupRows . current ) {
716746 if (
717- g > currentGroupRowsRef . current . size - 1 ||
747+ g > snapshotSize - 1 ||
718748 g > groupRowsRendered . length - 1
719749 ) {
720750 changedGroupRows . current . delete ( g )
@@ -723,8 +753,8 @@ export default function TimeTableRows<
723753 // unrender not visible rows, but render only if the placeholders are already rendered)
724754 renderGroupRows (
725755 renderGroupRangeRef . current ,
726- currentGroupRowsRef . current ,
727- groupRowKeys ,
756+ snapshotMap ,
757+ snapshotKeys ,
728758 g ,
729759 refCollection . current ,
730760 groupRowsRendered ,
@@ -752,13 +782,13 @@ export default function TimeTableRows<
752782
753783 let counter = 0
754784 while (
755- groupRowsRendered . length < currentGroupRowsRef . current . size &&
785+ groupRowsRendered . length < snapshotSize &&
756786 counter < timeTableGroupRenderBatchSize
757787 ) {
758788 renderGroupRows (
759789 renderGroupRangeRef . current ,
760- currentGroupRowsRef . current ,
761- groupRowKeys ,
790+ snapshotMap ,
791+ snapshotKeys ,
762792 groupRowsRendered . length ,
763793 refCollection . current ,
764794 groupRowsRendered ,
@@ -779,8 +809,8 @@ export default function TimeTableRows<
779809 )
780810 ++ counter
781811 }
782- if ( groupRowsRendered . length > currentGroupRowsRef . current . size ) {
783- groupRowsRendered . length = currentGroupRowsRef . current . size
812+ if ( groupRowsRendered . length > snapshotSize ) {
813+ groupRowsRendered . length = snapshotSize
784814 }
785815 rateLimiterIntersection ( handleIntersections )
786816 return groupRowsRendered
@@ -799,8 +829,6 @@ export default function TimeTableRows<
799829 rateLimiterIntersection ,
800830 timeStepMinutesHoursView ,
801831 onRenderedGroupsChanged ,
802- groupRowKeys ,
803- groupRows ,
804832 onTimeSlotClick ,
805833 ] )
806834
@@ -847,7 +875,7 @@ export default function TimeTableRows<
847875 const group = groupRowKeys [ groupIndex ]
848876 if ( group ) {
849877 const rows = groupRows . get ( group )
850- const currentRows = currentGroupRowsRef . current . get ( group )
878+ const currentRows = previousGroupRowsForRetry . current . get ( group )
851879 // If we previously had null data and now have actual data, trigger re-render
852880 if ( currentRows === null && rows !== null ) {
853881 hasNewData = true
0 commit comments