@@ -11,6 +11,7 @@ import {
1111 Platform ,
1212 FlatList ,
1313 ListRenderItemInfo ,
14+ ViewToken ,
1415} from 'react-native' ;
1516import TabBarItem , { Props as TabBarItemProps } from './TabBarItem' ;
1617import TabBarIndicator , { Props as IndicatorProps } from './TabBarIndicator' ;
@@ -23,6 +24,7 @@ import type {
2324 Event ,
2425} from './types' ;
2526import useAnimatedValue from './useAnimatedValue' ;
27+ import useLatestCallback from 'use-latest-callback' ;
2628
2729export type Props < T extends Route > = SceneRendererProps & {
2830 navigationState : NavigationState < T > ;
@@ -247,6 +249,10 @@ const renderIndicatorDefault = (props: IndicatorProps<Route>) => (
247249
248250const getTestIdDefault = ( { route } : Scene < Route > ) => route . testID ;
249251
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+
250256export default function TabBar < T extends Route > ( {
251257 getLabelText = getLabelTextDefault ,
252258 getAccessible = getAccessibleDefault ,
@@ -279,7 +285,7 @@ export default function TabBar<T extends Route>({
279285} : Props < T > ) {
280286 const [ layout , setLayout ] = React . useState < Layout > ( { width : 0 , height : 0 } ) ;
281287 const [ tabWidths , setTabWidths ] = React . useState < Record < string , number > > ( { } ) ;
282- const flatListRef = React . useRef < FlatList > ( null ) ;
288+ const flatListRef = React . useRef < FlatList | null > ( null ) ;
283289 const isFirst = React . useRef ( true ) ;
284290 const scrollAmount = useAnimatedValue ( 0 ) ;
285291 const measuredTabWidths = React . useRef < Record < string , number > > ( { } ) ;
@@ -296,28 +302,19 @@ export default function TabBar<T extends Route>({
296302 flattenedTabWidth,
297303 } ) ;
298304
299- const hasMeasuredTabWidths =
300- Boolean ( layout . width ) &&
301- routes . every ( ( r ) => typeof tabWidths [ r . key ] === 'number' ) ;
302-
303305 React . useEffect ( ( ) => {
304306 if ( isFirst . current ) {
305307 isFirst . current = false ;
306308 return ;
307309 }
308310
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-
314311 if ( scrollEnabled ) {
315312 flatListRef . current ?. scrollToOffset ( {
316313 offset : scrollOffset ,
317314 animated : true ,
318315 } ) ;
319316 }
320- } , [ hasMeasuredTabWidths , isWidthDynamic , scrollEnabled , scrollOffset ] ) ;
317+ } , [ scrollEnabled , scrollOffset ] ) ;
321318
322319 const handleLayout = ( e : LayoutChangeEvent ) => {
323320 const { height, width } = e . nativeEvent . layout ;
@@ -373,13 +370,24 @@ export default function TabBar<T extends Route>({
373370 ? ( e : LayoutChangeEvent ) => {
374371 measuredTabWidths . current [ route . key ] = e . nativeEvent . layout . width ;
375372
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.
378374 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 (
379385 routes . every (
380386 ( r ) => typeof measuredTabWidths . current [ r . key ] === 'number'
381387 )
382388 ) {
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
383391 setTabWidths ( { ...measuredTabWidths . current } ) ;
384392 }
385393 }
@@ -494,6 +502,25 @@ export default function TabBar<T extends Route>({
494502 [ scrollAmount ]
495503 ) ;
496504
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+
497524 return (
498525 < Animated . View onLayout = { handleLayout } style = { [ styles . tabBar , style ] } >
499526 < Animated . View
@@ -535,6 +562,7 @@ export default function TabBar<T extends Route>({
535562 data = { routes as Animated . WithAnimatedValue < T > [ ] }
536563 keyExtractor = { keyExtractor }
537564 horizontal
565+ initialNumToRender = { MEASURE_PER_BATCH }
538566 accessibilityRole = "tablist"
539567 keyboardShouldPersistTaps = "handled"
540568 scrollEnabled = { scrollEnabled }
@@ -549,6 +577,7 @@ export default function TabBar<T extends Route>({
549577 scrollEventThrottle = { 16 }
550578 renderItem = { renderItem }
551579 onScroll = { handleScroll }
580+ onViewableItemsChanged = { handleViewableItemsChanged }
552581 ref = { flatListRef }
553582 testID = { testID }
554583 />
0 commit comments