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

Commit

Permalink
feat: split updating state to batches on long lists
Browse files Browse the repository at this point in the history
  • Loading branch information
okwasniewski committed Nov 16, 2022
1 parent cafe21c commit e1693a2
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 8 deletions.
49 changes: 45 additions & 4 deletions src/TabBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -247,6 +248,8 @@ const renderIndicatorDefault = (props: IndicatorProps<Route>) => (

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

const MEASURE_PER_BATCH = 10;

export default function TabBar<T extends Route>({
getLabelText = getLabelTextDefault,
getAccessible = getAccessibleDefault,
Expand Down Expand Up @@ -279,8 +282,9 @@ export default function TabBar<T extends Route>({
}: Props<T>) {
const [layout, setLayout] = React.useState<Layout>({ width: 0, height: 0 });
const [tabWidths, setTabWidths] = React.useState<Record<string, number>>({});
const flatListRef = React.useRef<FlatList>(null);
const flatListRef = React.useRef<FlatList | null>(null);
const isFirst = React.useRef(true);
const measuredTabWidhtsCount = React.useRef(0);
const scrollAmount = useAnimatedValue(0);
const measuredTabWidths = React.useRef<Record<string, number>>({});

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

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) {
Expand Down Expand Up @@ -373,13 +384,25 @@ export default function TabBar<T extends Route>({
? (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 });
}
}
Expand Down Expand Up @@ -494,6 +517,22 @@ export default function TabBar<T extends Route>({
[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 (
<Animated.View onLayout={handleLayout} style={[styles.tabBar, style]}>
<Animated.View
Expand All @@ -513,6 +552,7 @@ export default function TabBar<T extends Route>({
position,
layout,
navigationState,
hasMeasuredTabWidths,
jumpTo,
width: isWidthDynamic
? 'auto'
Expand Down Expand Up @@ -549,6 +589,7 @@ export default function TabBar<T extends Route>({
scrollEventThrottle={16}
renderItem={renderItem}
onScroll={handleScroll}
onViewableItemsChanged={handleViewableItemsChanged}
ref={flatListRef}
testID={testID}
/>
Expand Down
6 changes: 2 additions & 4 deletions src/TabBarIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type Props<T extends Route> = SceneRendererProps & {
navigationState: NavigationState<T>;
width: string | number;
style?: StyleProp<ViewStyle>;
hasMeasuredTabWidths?: boolean;
getTabWidth: GetTabWidth;
gap?: number;
};
Expand Down Expand Up @@ -51,6 +52,7 @@ export default function TabBarIndicator<T extends Route>({
navigationState,
position,
width,
hasMeasuredTabWidths,
gap,
style,
}: Props<T>) {
Expand All @@ -59,10 +61,6 @@ export default function TabBarIndicator<T extends Route>({

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

const hasMeasuredTabWidths =
Boolean(layout.width) &&
navigationState.routes.every((_, i) => getTabWidth(i));

React.useEffect(() => {
const fadeInIndicator = () => {
if (
Expand Down

0 comments on commit e1693a2

Please sign in to comment.