Skip to content
This repository was archived by the owner on Nov 27, 2022. It is now read-only.

Commit 35d13cb

Browse files
committed
feat: split updating state to batches on long lists
1 parent cafe21c commit 35d13cb

File tree

2 files changed

+52
-10
lines changed

2 files changed

+52
-10
lines changed

src/TabBar.tsx

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Platform,
1212
FlatList,
1313
ListRenderItemInfo,
14+
ViewToken,
1415
} from 'react-native';
1516
import TabBarItem, { Props as TabBarItemProps } from './TabBarItem';
1617
import TabBarIndicator, { Props as IndicatorProps } from './TabBarIndicator';
@@ -247,6 +248,10 @@ const renderIndicatorDefault = (props: IndicatorProps<Route>) => (
247248

248249
const getTestIdDefault = ({ route }: Scene<Route>) => route.testID;
249250

251+
// How many items measurements should we update per batch.
252+
// Defaults to 10, since that's whats FlatList is using in initialNumToRender.
253+
const MEASURE_PER_BATCH = 10;
254+
250255
export default function TabBar<T extends Route>({
251256
getLabelText = getLabelTextDefault,
252257
getAccessible = getAccessibleDefault,
@@ -279,8 +284,9 @@ export default function TabBar<T extends Route>({
279284
}: Props<T>) {
280285
const [layout, setLayout] = React.useState<Layout>({ width: 0, height: 0 });
281286
const [tabWidths, setTabWidths] = React.useState<Record<string, number>>({});
282-
const flatListRef = React.useRef<FlatList>(null);
287+
const flatListRef = React.useRef<FlatList | null>(null);
283288
const isFirst = React.useRef(true);
289+
const howManyMeasured = React.useRef(0);
284290
const scrollAmount = useAnimatedValue(0);
285291
const measuredTabWidths = React.useRef<Record<string, number>>({});
286292

@@ -298,7 +304,14 @@ export default function TabBar<T extends Route>({
298304

299305
const hasMeasuredTabWidths =
300306
Boolean(layout.width) &&
301-
routes.every((r) => typeof tabWidths[r.key] === 'number');
307+
routes
308+
.slice(
309+
0,
310+
routes.length > MEASURE_PER_BATCH
311+
? howManyMeasured.current
312+
: routes.length
313+
)
314+
.every((r) => typeof tabWidths[r.key] === 'number');
302315

303316
React.useEffect(() => {
304317
if (isFirst.current) {
@@ -373,13 +386,25 @@ export default function TabBar<T extends Route>({
373386
? (e: LayoutChangeEvent) => {
374387
measuredTabWidths.current[route.key] = e.nativeEvent.layout.width;
375388

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
389+
// If we have more than 10 routes divide updating tabWidths into multiple batches. Here we update only first batch of 10 items.
378390
if (
391+
routes.length > MEASURE_PER_BATCH &&
392+
index === MEASURE_PER_BATCH &&
393+
routes
394+
.slice(0, MEASURE_PER_BATCH)
395+
.every(
396+
(r) => typeof measuredTabWidths.current[r.key] === 'number'
397+
)
398+
) {
399+
setTabWidths({ ...measuredTabWidths.current });
400+
howManyMeasured.current = MEASURE_PER_BATCH;
401+
} else if (
379402
routes.every(
380403
(r) => typeof measuredTabWidths.current[r.key] === 'number'
381404
)
382405
) {
406+
// When we have measured widths for all of the tabs, we should updates the state
407+
// We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
383408
setTabWidths({ ...measuredTabWidths.current });
384409
}
385410
}
@@ -494,6 +519,22 @@ export default function TabBar<T extends Route>({
494519
[scrollAmount]
495520
);
496521

522+
const handleViewableItemsChanged = React.useCallback(
523+
({ changed }: { changed: ViewToken[] }) => {
524+
if (routes.length <= MEASURE_PER_BATCH) {
525+
return;
526+
}
527+
// Get next vievable item
528+
const [item] = changed;
529+
const index = item.index || 0;
530+
if (item.isViewable && index >= howManyMeasured.current) {
531+
setTabWidths({ ...measuredTabWidths.current });
532+
howManyMeasured.current += MEASURE_PER_BATCH;
533+
}
534+
},
535+
[routes.length]
536+
);
537+
497538
return (
498539
<Animated.View onLayout={handleLayout} style={[styles.tabBar, style]}>
499540
<Animated.View
@@ -513,6 +554,7 @@ export default function TabBar<T extends Route>({
513554
position,
514555
layout,
515556
navigationState,
557+
visible: hasMeasuredTabWidths,
516558
jumpTo,
517559
width: isWidthDynamic
518560
? 'auto'
@@ -535,6 +577,7 @@ export default function TabBar<T extends Route>({
535577
data={routes as Animated.WithAnimatedValue<T>[]}
536578
keyExtractor={keyExtractor}
537579
horizontal
580+
initialNumToRender={MEASURE_PER_BATCH}
538581
accessibilityRole="tablist"
539582
keyboardShouldPersistTaps="handled"
540583
scrollEnabled={scrollEnabled}
@@ -549,6 +592,7 @@ export default function TabBar<T extends Route>({
549592
scrollEventThrottle={16}
550593
renderItem={renderItem}
551594
onScroll={handleScroll}
595+
onViewableItemsChanged={handleViewableItemsChanged}
552596
ref={flatListRef}
553597
testID={testID}
554598
/>

src/TabBarIndicator.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export type Props<T extends Route> = SceneRendererProps & {
1818
navigationState: NavigationState<T>;
1919
width: string | number;
2020
style?: StyleProp<ViewStyle>;
21+
visible?: boolean;
2122
getTabWidth: GetTabWidth;
2223
gap?: number;
2324
};
@@ -51,6 +52,7 @@ export default function TabBarIndicator<T extends Route>({
5152
navigationState,
5253
position,
5354
width,
55+
visible,
5456
gap,
5557
style,
5658
}: Props<T>) {
@@ -59,17 +61,13 @@ export default function TabBarIndicator<T extends Route>({
5961

6062
const opacity = useAnimatedValue(isWidthDynamic ? 0 : 1);
6163

62-
const hasMeasuredTabWidths =
63-
Boolean(layout.width) &&
64-
navigationState.routes.every((_, i) => getTabWidth(i));
65-
6664
React.useEffect(() => {
6765
const fadeInIndicator = () => {
6866
if (
6967
!isIndicatorShown.current &&
7068
isWidthDynamic &&
7169
// We should fade-in the indicator when we have widths for all the tab items
72-
hasMeasuredTabWidths
70+
visible
7371
) {
7472
isIndicatorShown.current = true;
7573

@@ -85,7 +83,7 @@ export default function TabBarIndicator<T extends Route>({
8583
fadeInIndicator();
8684

8785
return () => opacity.stopAnimation();
88-
}, [hasMeasuredTabWidths, isWidthDynamic, opacity]);
86+
}, [visible, isWidthDynamic, opacity]);
8987

9088
const { routes } = navigationState;
9189

0 commit comments

Comments
 (0)