@@ -11,6 +11,7 @@ import {
11
11
Platform ,
12
12
FlatList ,
13
13
ListRenderItemInfo ,
14
+ ViewToken ,
14
15
} from 'react-native' ;
15
16
import TabBarItem , { Props as TabBarItemProps } from './TabBarItem' ;
16
17
import TabBarIndicator , { Props as IndicatorProps } from './TabBarIndicator' ;
@@ -23,6 +24,7 @@ import type {
23
24
Event ,
24
25
} from './types' ;
25
26
import useAnimatedValue from './useAnimatedValue' ;
27
+ import useLatestCallback from 'use-latest-callback' ;
26
28
27
29
export type Props < T extends Route > = SceneRendererProps & {
28
30
navigationState : NavigationState < T > ;
@@ -247,6 +249,10 @@ const renderIndicatorDefault = (props: IndicatorProps<Route>) => (
247
249
248
250
const getTestIdDefault = ( { route } : Scene < Route > ) => route . testID ;
249
251
252
+ // How many items measurements should we update per batch.
253
+ // Defaults to 10, since that's whats FlatList is using in initialNumToRender.
254
+ const MEASURE_PER_BATCH = 10 ;
255
+
250
256
export default function TabBar < T extends Route > ( {
251
257
getLabelText = getLabelTextDefault ,
252
258
getAccessible = getAccessibleDefault ,
@@ -279,7 +285,7 @@ export default function TabBar<T extends Route>({
279
285
} : Props < T > ) {
280
286
const [ layout , setLayout ] = React . useState < Layout > ( { width : 0 , height : 0 } ) ;
281
287
const [ tabWidths , setTabWidths ] = React . useState < Record < string , number > > ( { } ) ;
282
- const flatListRef = React . useRef < FlatList > ( null ) ;
288
+ const flatListRef = React . useRef < FlatList | null > ( null ) ;
283
289
const isFirst = React . useRef ( true ) ;
284
290
const scrollAmount = useAnimatedValue ( 0 ) ;
285
291
const measuredTabWidths = React . useRef < Record < string , number > > ( { } ) ;
@@ -296,28 +302,19 @@ export default function TabBar<T extends Route>({
296
302
flattenedTabWidth,
297
303
} ) ;
298
304
299
- const hasMeasuredTabWidths =
300
- Boolean ( layout . width ) &&
301
- routes . every ( ( r ) => typeof tabWidths [ r . key ] === 'number' ) ;
302
-
303
305
React . useEffect ( ( ) => {
304
306
if ( isFirst . current ) {
305
307
isFirst . current = false ;
306
308
return ;
307
309
}
308
310
309
- if ( isWidthDynamic && ! hasMeasuredTabWidths ) {
310
- // When tab width is dynamic, only adjust the scroll once we have all tab widths and layout
311
- return ;
312
- }
313
-
314
311
if ( scrollEnabled ) {
315
312
flatListRef . current ?. scrollToOffset ( {
316
313
offset : scrollOffset ,
317
314
animated : true ,
318
315
} ) ;
319
316
}
320
- } , [ hasMeasuredTabWidths , isWidthDynamic , scrollEnabled , scrollOffset ] ) ;
317
+ } , [ scrollEnabled , scrollOffset ] ) ;
321
318
322
319
const handleLayout = ( e : LayoutChangeEvent ) => {
323
320
const { height, width } = e . nativeEvent . layout ;
@@ -373,13 +370,24 @@ export default function TabBar<T extends Route>({
373
370
? ( e : LayoutChangeEvent ) => {
374
371
measuredTabWidths . current [ route . key ] = e . nativeEvent . layout . width ;
375
372
376
- // When we have measured widths for all of the tabs, we should updates the state
377
- // We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
373
+ // If we have more than 10 routes divide updating tabWidths into multiple batches. Here we update only first batch of 10 items.
378
374
if (
375
+ routes . length > MEASURE_PER_BATCH &&
376
+ index === MEASURE_PER_BATCH &&
377
+ routes
378
+ . slice ( 0 , MEASURE_PER_BATCH )
379
+ . every (
380
+ ( r ) => typeof measuredTabWidths . current [ r . key ] === 'number'
381
+ )
382
+ ) {
383
+ setTabWidths ( { ...measuredTabWidths . current } ) ;
384
+ } else if (
379
385
routes . every (
380
386
( r ) => typeof measuredTabWidths . current [ r . key ] === 'number'
381
387
)
382
388
) {
389
+ // When we have measured widths for all of the tabs, we should updates the state
390
+ // We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
383
391
setTabWidths ( { ...measuredTabWidths . current } ) ;
384
392
}
385
393
}
@@ -494,6 +502,25 @@ export default function TabBar<T extends Route>({
494
502
[ scrollAmount ]
495
503
) ;
496
504
505
+ const handleViewableItemsChanged = useLatestCallback (
506
+ ( { changed } : { changed : ViewToken [ ] } ) => {
507
+ if ( routes . length <= MEASURE_PER_BATCH ) {
508
+ return ;
509
+ }
510
+ // Get next vievable item
511
+ const item = changed [ changed . length - 1 ] ;
512
+ const index = item ?. index || 0 ;
513
+ if (
514
+ item . isViewable &&
515
+ ( index % 10 === 0 ||
516
+ index === navigationState . index ||
517
+ index === routes . length - 1 )
518
+ ) {
519
+ setTabWidths ( { ...measuredTabWidths . current } ) ;
520
+ }
521
+ }
522
+ ) ;
523
+
497
524
return (
498
525
< Animated . View onLayout = { handleLayout } style = { [ styles . tabBar , style ] } >
499
526
< Animated . View
@@ -535,6 +562,7 @@ export default function TabBar<T extends Route>({
535
562
data = { routes as Animated . WithAnimatedValue < T > [ ] }
536
563
keyExtractor = { keyExtractor }
537
564
horizontal
565
+ initialNumToRender = { MEASURE_PER_BATCH }
538
566
accessibilityRole = "tablist"
539
567
keyboardShouldPersistTaps = "handled"
540
568
scrollEnabled = { scrollEnabled }
@@ -549,6 +577,7 @@ export default function TabBar<T extends Route>({
549
577
scrollEventThrottle = { 16 }
550
578
renderItem = { renderItem }
551
579
onScroll = { handleScroll }
580
+ onViewableItemsChanged = { handleViewableItemsChanged }
552
581
ref = { flatListRef }
553
582
testID = { testID }
554
583
/>
0 commit comments