diff --git a/src/TabBar.tsx b/src/TabBar.tsx index 9025bd48..c398c5c5 100644 --- a/src/TabBar.tsx +++ b/src/TabBar.tsx @@ -11,6 +11,7 @@ import { Platform, FlatList, ListRenderItemInfo, + ViewToken, } from 'react-native'; import TabBarItem, { Props as TabBarItemProps } from './TabBarItem'; import TabBarIndicator, { Props as IndicatorProps } from './TabBarIndicator'; @@ -247,6 +248,8 @@ const renderIndicatorDefault = (props: IndicatorProps) => ( const getTestIdDefault = ({ route }: Scene) => route.testID; +const MEASURE_PER_BATCH = 10; + export default function TabBar({ getLabelText = getLabelTextDefault, getAccessible = getAccessibleDefault, @@ -279,8 +282,9 @@ export default function TabBar({ }: Props) { const [layout, setLayout] = React.useState({ width: 0, height: 0 }); const [tabWidths, setTabWidths] = React.useState>({}); - const flatListRef = React.useRef(null); + const flatListRef = React.useRef(null); const isFirst = React.useRef(true); + const measuredTabWidhtsCount = React.useRef(0); const scrollAmount = useAnimatedValue(0); const measuredTabWidths = React.useRef>({}); @@ -298,7 +302,14 @@ export default function TabBar({ const hasMeasuredTabWidths = Boolean(layout.width) && - routes.every((r) => typeof tabWidths[r.key] === 'number'); + routes + .slice( + 0, + routes.length > MEASURE_PER_BATCH + ? measuredTabWidhtsCount.current + : routes.length + ) + .every((r) => typeof tabWidths[r.key] === 'number'); React.useEffect(() => { if (isFirst.current) { @@ -373,13 +384,25 @@ export default function TabBar({ ? (e: LayoutChangeEvent) => { measuredTabWidths.current[route.key] = e.nativeEvent.layout.width; - // When we have measured widths for all of the tabs, we should updates the state - // We avoid doing separate setState for each layout since it triggers multiple renders and slows down app + // If we have more than 10 routes divide updating tabWidths into multiple batches. Here we update only first batch of 10 items. if ( + routes.length > MEASURE_PER_BATCH && + index === MEASURE_PER_BATCH && + routes + .slice(0, MEASURE_PER_BATCH) + .every( + (r) => typeof measuredTabWidths.current[r.key] === 'number' + ) + ) { + setTabWidths({ ...measuredTabWidths.current }); + measuredTabWidhtsCount.current = MEASURE_PER_BATCH; + } else if ( routes.every( (r) => typeof measuredTabWidths.current[r.key] === 'number' ) ) { + // When we have measured widths for all of the tabs, we should updates the state + // We avoid doing separate setState for each layout since it triggers multiple renders and slows down app setTabWidths({ ...measuredTabWidths.current }); } } @@ -494,6 +517,22 @@ export default function TabBar({ [scrollAmount] ); + const handleViewableItemsChanged = React.useCallback( + ({ changed }: { changed: ViewToken[] }) => { + if (routes.length <= MEASURE_PER_BATCH) { + return; + } + // Get next vievable item + const [item] = changed; + const index = item.index || 0; + if (item.isViewable && index >= measuredTabWidhtsCount.current) { + setTabWidths({ ...measuredTabWidths.current }); + measuredTabWidhtsCount.current += MEASURE_PER_BATCH; + } + }, + [routes.length] + ); + return ( ({ position, layout, navigationState, + hasMeasuredTabWidths, jumpTo, width: isWidthDynamic ? 'auto' @@ -549,6 +589,7 @@ export default function TabBar({ scrollEventThrottle={16} renderItem={renderItem} onScroll={handleScroll} + onViewableItemsChanged={handleViewableItemsChanged} ref={flatListRef} testID={testID} /> diff --git a/src/TabBarIndicator.tsx b/src/TabBarIndicator.tsx index 8076d300..0763b24f 100644 --- a/src/TabBarIndicator.tsx +++ b/src/TabBarIndicator.tsx @@ -18,6 +18,7 @@ export type Props = SceneRendererProps & { navigationState: NavigationState; width: string | number; style?: StyleProp; + hasMeasuredTabWidths?: boolean; getTabWidth: GetTabWidth; gap?: number; }; @@ -51,6 +52,7 @@ export default function TabBarIndicator({ navigationState, position, width, + hasMeasuredTabWidths, gap, style, }: Props) { @@ -59,10 +61,6 @@ export default function TabBarIndicator({ const opacity = useAnimatedValue(isWidthDynamic ? 0 : 1); - const hasMeasuredTabWidths = - Boolean(layout.width) && - navigationState.routes.every((_, i) => getTabWidth(i)); - React.useEffect(() => { const fadeInIndicator = () => { if (