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

Commit e1693a2

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

File tree

2 files changed

+47
-8
lines changed

2 files changed

+47
-8
lines changed

src/TabBar.tsx

Lines changed: 45 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,8 @@ const renderIndicatorDefault = (props: IndicatorProps<Route>) => (
247248

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

251+
const MEASURE_PER_BATCH = 10;
252+
250253
export default function TabBar<T extends Route>({
251254
getLabelText = getLabelTextDefault,
252255
getAccessible = getAccessibleDefault,
@@ -279,8 +282,9 @@ export default function TabBar<T extends Route>({
279282
}: Props<T>) {
280283
const [layout, setLayout] = React.useState<Layout>({ width: 0, height: 0 });
281284
const [tabWidths, setTabWidths] = React.useState<Record<string, number>>({});
282-
const flatListRef = React.useRef<FlatList>(null);
285+
const flatListRef = React.useRef<FlatList | null>(null);
283286
const isFirst = React.useRef(true);
287+
const measuredTabWidhtsCount = React.useRef(0);
284288
const scrollAmount = useAnimatedValue(0);
285289
const measuredTabWidths = React.useRef<Record<string, number>>({});
286290

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

299303
const hasMeasuredTabWidths =
300304
Boolean(layout.width) &&
301-
routes.every((r) => typeof tabWidths[r.key] === 'number');
305+
routes
306+
.slice(
307+
0,
308+
routes.length > MEASURE_PER_BATCH
309+
? measuredTabWidhtsCount.current
310+
: routes.length
311+
)
312+
.every((r) => typeof tabWidths[r.key] === 'number');
302313

303314
React.useEffect(() => {
304315
if (isFirst.current) {
@@ -373,13 +384,25 @@ export default function TabBar<T extends Route>({
373384
? (e: LayoutChangeEvent) => {
374385
measuredTabWidths.current[route.key] = e.nativeEvent.layout.width;
375386

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
387+
// If we have more than 10 routes divide updating tabWidths into multiple batches. Here we update only first batch of 10 items.
378388
if (
389+
routes.length > MEASURE_PER_BATCH &&
390+
index === MEASURE_PER_BATCH &&
391+
routes
392+
.slice(0, MEASURE_PER_BATCH)
393+
.every(
394+
(r) => typeof measuredTabWidths.current[r.key] === 'number'
395+
)
396+
) {
397+
setTabWidths({ ...measuredTabWidths.current });
398+
measuredTabWidhtsCount.current = MEASURE_PER_BATCH;
399+
} else if (
379400
routes.every(
380401
(r) => typeof measuredTabWidths.current[r.key] === 'number'
381402
)
382403
) {
404+
// When we have measured widths for all of the tabs, we should updates the state
405+
// We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
383406
setTabWidths({ ...measuredTabWidths.current });
384407
}
385408
}
@@ -494,6 +517,22 @@ export default function TabBar<T extends Route>({
494517
[scrollAmount]
495518
);
496519

520+
const handleViewableItemsChanged = React.useCallback(
521+
({ changed }: { changed: ViewToken[] }) => {
522+
if (routes.length <= MEASURE_PER_BATCH) {
523+
return;
524+
}
525+
// Get next vievable item
526+
const [item] = changed;
527+
const index = item.index || 0;
528+
if (item.isViewable && index >= measuredTabWidhtsCount.current) {
529+
setTabWidths({ ...measuredTabWidths.current });
530+
measuredTabWidhtsCount.current += MEASURE_PER_BATCH;
531+
}
532+
},
533+
[routes.length]
534+
);
535+
497536
return (
498537
<Animated.View onLayout={handleLayout} style={[styles.tabBar, style]}>
499538
<Animated.View
@@ -513,6 +552,7 @@ export default function TabBar<T extends Route>({
513552
position,
514553
layout,
515554
navigationState,
555+
hasMeasuredTabWidths,
516556
jumpTo,
517557
width: isWidthDynamic
518558
? 'auto'
@@ -549,6 +589,7 @@ export default function TabBar<T extends Route>({
549589
scrollEventThrottle={16}
550590
renderItem={renderItem}
551591
onScroll={handleScroll}
592+
onViewableItemsChanged={handleViewableItemsChanged}
552593
ref={flatListRef}
553594
testID={testID}
554595
/>

src/TabBarIndicator.tsx

Lines changed: 2 additions & 4 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+
hasMeasuredTabWidths?: 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+
hasMeasuredTabWidths,
5456
gap,
5557
style,
5658
}: Props<T>) {
@@ -59,10 +61,6 @@ 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 (

0 commit comments

Comments
 (0)