From 1f94c8b7b2e11f09a36001ce7b512ec9468a63b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwa=C5=9Bniewski?= Date: Wed, 21 Jun 2023 15:14:50 +0200 Subject: [PATCH] fix: optimize tabBarItem by memoizing getter functions (#11427) **Motivation** The goal of this PR is to memoize the result of getter functions passed to TabBarItem like: `getAccessibilityLabel()`. This is currently _the best_ we can do to optimize material-top-tabs re-renders. **Test plan** Go to Material top tabs example and examine re-renders using React Profiler. --- .../react-native-tab-view/src/TabBarItem.tsx | 92 ++++++++++--------- .../src/TabBarItemLabel.tsx | 39 ++++++++ 2 files changed, 89 insertions(+), 42 deletions(-) create mode 100644 packages/react-native-tab-view/src/TabBarItemLabel.tsx diff --git a/packages/react-native-tab-view/src/TabBarItem.tsx b/packages/react-native-tab-view/src/TabBarItem.tsx index c4781e2d19..c1c0d77114 100644 --- a/packages/react-native-tab-view/src/TabBarItem.tsx +++ b/packages/react-native-tab-view/src/TabBarItem.tsx @@ -12,6 +12,7 @@ import { import useLatestCallback from 'use-latest-callback'; import { PlatformPressable } from './PlatformPressable'; +import { TabBarItemLabel } from './TabBarItemLabel'; import type { NavigationState, Route, Scene } from './types'; export type Props = { @@ -85,18 +86,26 @@ const getInactiveOpacity = ( type TabBarItemInternalProps = Omit< Props, - 'navigationState' + | 'navigationState' + | 'getAccessibilityLabel' + | 'getLabelText' + | 'getTestID' + | 'getAccessible' > & { isFocused: boolean; index: number; routesLength: number; + accessibilityLabel?: string; + label?: string; + testID?: string; + accessible?: boolean; }; const TabBarItemInternal = ({ - getAccessibilityLabel, - getAccessible, - getLabelText, - getTestID, + accessibilityLabel, + accessible, + label: labelText, + testID, onLongPress, onPress, isFocused, @@ -166,29 +175,16 @@ const TabBarItemInternal = ({ } } - const renderLabel = - renderLabelCustom !== undefined - ? renderLabelCustom - : (labelProps: { route: T; color: string }) => { - const labelText = getLabelText({ route: labelProps.route }); - - if (typeof labelText === 'string') { - return ( - - {labelText} - - ); - } - - return labelText; - }; + const renderLabel = renderLabelCustom + ? renderLabelCustom + : (labelProps: { color: string }) => ( + + ); if (renderLabel) { const activeLabel = renderLabel({ @@ -225,20 +221,16 @@ const TabBarItemInternal = ({ const scene = { route }; - let accessibilityLabel = getAccessibilityLabel(scene); - accessibilityLabel = - typeof accessibilityLabel !== 'undefined' - ? accessibilityLabel - : getLabelText(scene); + typeof accessibilityLabel !== 'undefined' ? accessibilityLabel : labelText; const badge = renderBadge ? renderBadge(scene) : null; return ( (props: Props) { - const { onPress, onLongPress, onLayout, navigationState, route, ...rest } = - props; + const { + onPress, + onLongPress, + onLayout, + navigationState, + route, + getAccessibilityLabel, + getLabelText, + getTestID, + getAccessible, + ...rest + } = props; const onPressLatest = useLatestCallback(onPress); const onLongPressLatest = useLatestCallback(onLongPress); const onLayoutLatest = useLatestCallback(onLayout ? onLayout : () => {}); const tabIndex = navigationState.routes.indexOf(route); + const scene = { route }; + + const accessibilityLabel = getAccessibilityLabel(scene); + const label = getLabelText(scene); + const testID = getTestID(scene); + const accessible = getAccessible(scene); + return ( (props: Props) { route={route} index={tabIndex} routesLength={navigationState.routes.length} + accessibilityLabel={accessibilityLabel} + label={label} + testID={testID} + accessible={accessible} /> ); } const styles = StyleSheet.create({ - label: { - margin: 4, - backgroundColor: 'transparent', - textTransform: 'uppercase', - }, icon: { margin: 2, }, diff --git a/packages/react-native-tab-view/src/TabBarItemLabel.tsx b/packages/react-native-tab-view/src/TabBarItemLabel.tsx new file mode 100644 index 0000000000..3f1f5662a5 --- /dev/null +++ b/packages/react-native-tab-view/src/TabBarItemLabel.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import type { StyleProp, ViewStyle } from 'react-native'; +import { Animated, StyleSheet } from 'react-native'; + +interface TabBarItemLabelProps { + color: string; + label?: string; + labelStyle: StyleProp; + icon: React.ReactNode; +} + +export const TabBarItemLabel = React.memo( + ({ color, label, labelStyle, icon }: TabBarItemLabelProps) => { + if (!label) { + return null; + } + + return ( + + {label} + + ); + } +); + +const styles = StyleSheet.create({ + label: { + margin: 4, + backgroundColor: 'transparent', + textTransform: 'uppercase', + }, +});