From 1ef4f12c61bfe8a7fcc0770439c3292bda4f5879 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Fri, 13 Mar 2026 11:07:05 +0100 Subject: [PATCH 1/3] Fix - 'Expense - Report RHP re-renders every time clicking Create expense' --- .../TabSelector/TabSelectorContext.tsx | 14 ++++++++++- .../TabSelector/scrollToTab/index.native.ts | 24 ------------------- .../TabSelector/scrollToTab/index.ts | 20 +++++++++++++--- 3 files changed, 30 insertions(+), 28 deletions(-) delete mode 100644 src/components/TabSelector/scrollToTab/index.native.ts diff --git a/src/components/TabSelector/TabSelectorContext.tsx b/src/components/TabSelector/TabSelectorContext.tsx index 20135228d149f..e3fb470b354a4 100644 --- a/src/components/TabSelector/TabSelectorContext.tsx +++ b/src/components/TabSelector/TabSelectorContext.tsx @@ -36,6 +36,18 @@ function TabSelectorContextProvider({children, activeTabKey}: TabSelectorContext const onContainerLayout = (event: LayoutChangeEvent) => { const width = event.nativeEvent.layout.width; containerLayoutRef.current.width = width; + + const tabData = tabsRef.current[activeTabKey]; + + if (!tabData) { + return; + } + + const {ref: tabRef, x: tabX, width: tabWidth} = tabData; + + if (tabWidth) { + scrollToTabUtil({tabX, tabWidth, tabRef, containerRef, containerWidth: containerLayoutRef.current.width, containerX: containerLayoutRef.current.x, animated: false}); + } }; const onContainerScroll = (event: NativeSyntheticEvent) => { @@ -55,7 +67,7 @@ function TabSelectorContextProvider({children, activeTabKey}: TabSelectorContext const {x, width} = event.nativeEvent.layout; tabsRef.current[tabKey] = {...tabsRef.current[tabKey], x, width}; - if (tabKey === activeTabKey) { + if (tabKey === activeTabKey && containerLayoutRef.current.width !== 0) { const {ref: tabRef} = tabsRef.current[tabKey]; scrollToTabUtil({tabX: x, tabWidth: width, tabRef, containerRef, containerWidth: containerLayoutRef.current.width, containerX: containerLayoutRef.current.x, animated: false}); } diff --git a/src/components/TabSelector/scrollToTab/index.native.ts b/src/components/TabSelector/scrollToTab/index.native.ts deleted file mode 100644 index cf0c3f3b106e5..0000000000000 --- a/src/components/TabSelector/scrollToTab/index.native.ts +++ /dev/null @@ -1,24 +0,0 @@ -import variables from '@styles/variables'; -import type {ScrollToTabProps} from './types'; - -function scrollToTab({containerX, tabX, tabWidth, animated = true, containerRef, containerWidth}: ScrollToTabProps) { - if (!containerRef.current) { - return; - } - - const isTabLeftSideCut = containerX > tabX; - const isTabRightSideCut = tabX + tabWidth >= containerX + containerWidth - variables.tabSelectorScrollMarginInline; - if (!isTabLeftSideCut && !isTabRightSideCut) { - return; - } - - if (isTabRightSideCut) { - const tabCutLengthOnRight = tabX + tabWidth - (containerWidth + containerX); - containerRef.current.scrollTo({x: containerX + tabCutLengthOnRight + variables.tabSelectorScrollMarginInline, animated}); - return; - } - - containerRef.current.scrollTo({x: tabX - variables.tabSelectorScrollMarginInline, animated}); -} - -export default scrollToTab; diff --git a/src/components/TabSelector/scrollToTab/index.ts b/src/components/TabSelector/scrollToTab/index.ts index 4899a01c01b87..2c048c222e4a6 100644 --- a/src/components/TabSelector/scrollToTab/index.ts +++ b/src/components/TabSelector/scrollToTab/index.ts @@ -1,11 +1,25 @@ +import variables from '@styles/variables'; import type {ScrollToTabProps} from './types'; -function scrollToTab({tabRef, animated = true}: ScrollToTabProps) { - if (!tabRef || !('scrollIntoView' in tabRef)) { +function scrollToTab({containerX, tabX, tabWidth, animated = true, containerRef, containerWidth}: ScrollToTabProps) { + if (!containerRef.current) { return; } - tabRef.scrollIntoView({block: 'nearest', behavior: animated ? 'smooth' : 'instant'}); + const isTabLeftSideCut = containerX > tabX; + const isTabRightSideCut = tabX + tabWidth >= containerX + containerWidth - variables.tabSelectorScrollMarginInline; + + if (!isTabLeftSideCut && !isTabRightSideCut) { + return; + } + + if (isTabRightSideCut) { + const tabCutLengthOnRight = tabX + tabWidth - (containerWidth + containerX); + containerRef.current.scrollTo({x: containerX + tabCutLengthOnRight + variables.tabSelectorScrollMarginInline, animated}); + return; + } + + containerRef.current.scrollTo({x: tabX - variables.tabSelectorScrollMarginInline, animated}); } export default scrollToTab; From d0c0d6d591e91203d2e5705f1989a2ee344bf9cf Mon Sep 17 00:00:00 2001 From: GCyganek Date: Fri, 13 Mar 2026 11:26:31 +0100 Subject: [PATCH 2/3] delete tabRef and registerTab() --- .../TabSelector/TabSelectorContext.tsx | 24 +++++-------------- .../TabSelector/TabSelectorItem.tsx | 3 +-- .../TabSelector/scrollToTab/types.ts | 1 - 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/components/TabSelector/TabSelectorContext.tsx b/src/components/TabSelector/TabSelectorContext.tsx index e3fb470b354a4..dcedf2951aea4 100644 --- a/src/components/TabSelector/TabSelectorContext.tsx +++ b/src/components/TabSelector/TabSelectorContext.tsx @@ -8,7 +8,6 @@ type TabSelectorContextValue = { onContainerLayout: (event: LayoutChangeEvent) => void; onContainerScroll: (event: NativeSyntheticEvent) => void; scrollToTab: (tabKey: string) => void; - registerTab: (tabKey: string, ref: HTMLDivElement | View | null) => void; onTabLayout: (tabKey: string, event: LayoutChangeEvent) => void; }; @@ -22,7 +21,6 @@ const defaultValue: TabSelectorContextValue = { onContainerLayout: () => {}, onContainerScroll: () => {}, scrollToTab: () => {}, - registerTab: () => {}, onTabLayout: () => {}, }; @@ -31,7 +29,7 @@ const TabSelectorContext = createContext(defaultValue); function TabSelectorContextProvider({children, activeTabKey}: TabSelectorContextProviderProps) { const containerRef = useRef(null); const containerLayoutRef = useRef<{x: number; width: number}>({x: 0, width: 0}); - const tabsRef = useRef>({}); + const tabsRef = useRef>({}); const onContainerLayout = (event: LayoutChangeEvent) => { const width = event.nativeEvent.layout.width; @@ -43,10 +41,10 @@ function TabSelectorContextProvider({children, activeTabKey}: TabSelectorContext return; } - const {ref: tabRef, x: tabX, width: tabWidth} = tabData; + const {x: tabX, width: tabWidth} = tabData; if (tabWidth) { - scrollToTabUtil({tabX, tabWidth, tabRef, containerRef, containerWidth: containerLayoutRef.current.width, containerX: containerLayoutRef.current.x, animated: false}); + scrollToTabUtil({tabX, tabWidth, containerRef, containerWidth: containerLayoutRef.current.width, containerX: containerLayoutRef.current.x, animated: false}); } }; @@ -55,21 +53,12 @@ function TabSelectorContextProvider({children, activeTabKey}: TabSelectorContext containerLayoutRef.current.x = x; }; - const registerTab = (tabKey: string, ref: HTMLDivElement | View | null) => { - if (ref === null) { - return; - } - - tabsRef.current[tabKey] = {...tabsRef.current[tabKey], ref}; - }; - const onTabLayout = (tabKey: string, event: LayoutChangeEvent) => { const {x, width} = event.nativeEvent.layout; tabsRef.current[tabKey] = {...tabsRef.current[tabKey], x, width}; if (tabKey === activeTabKey && containerLayoutRef.current.width !== 0) { - const {ref: tabRef} = tabsRef.current[tabKey]; - scrollToTabUtil({tabX: x, tabWidth: width, tabRef, containerRef, containerWidth: containerLayoutRef.current.width, containerX: containerLayoutRef.current.x, animated: false}); + scrollToTabUtil({tabX: x, tabWidth: width, containerRef, containerWidth: containerLayoutRef.current.width, containerX: containerLayoutRef.current.x, animated: false}); } }; @@ -80,16 +69,15 @@ function TabSelectorContextProvider({children, activeTabKey}: TabSelectorContext return; } - const {x: tabX, width: tabWidth, ref: tabRef} = tabData; + const {x: tabX, width: tabWidth} = tabData; - scrollToTabUtil({tabX, tabWidth, tabRef, containerRef, containerWidth: containerLayoutRef.current.width, containerX: containerLayoutRef.current.x}); + scrollToTabUtil({tabX, tabWidth, containerRef, containerWidth: containerLayoutRef.current.width, containerX: containerLayoutRef.current.x}); }; // React Compiler auto-memoization // eslint-disable-next-line react/jsx-no-constructed-context-values const contextValue = { containerRef, - registerTab, onTabLayout, onContainerLayout, onContainerScroll, diff --git a/src/components/TabSelector/TabSelectorItem.tsx b/src/components/TabSelector/TabSelectorItem.tsx index 0b6c290736bc8..eef4faba7eda3 100644 --- a/src/components/TabSelector/TabSelectorItem.tsx +++ b/src/components/TabSelector/TabSelectorItem.tsx @@ -37,13 +37,12 @@ function TabSelectorItem({ const [isHovered, setIsHovered] = useState(false); const shouldShowEducationalTooltip = shouldShowProductTrainingTooltip && isActive; - const {onTabLayout, registerTab, scrollToTab} = useContext(TabSelectorContext); + const {onTabLayout, scrollToTab} = useContext(TabSelectorContext); const accessibilityState = {selected: isActive}; const children = ( registerTab(tabKey, ref)} accessibilityLabel={title} accessibilityState={accessibilityState} accessibilityRole={CONST.ROLE.TAB} diff --git a/src/components/TabSelector/scrollToTab/types.ts b/src/components/TabSelector/scrollToTab/types.ts index 5aaa949f04ab1..23dafe994ca0c 100644 --- a/src/components/TabSelector/scrollToTab/types.ts +++ b/src/components/TabSelector/scrollToTab/types.ts @@ -3,7 +3,6 @@ import type {ScrollView as RNScrollView, View} from 'react-native'; type ScrollToTabProps = { animated?: boolean; - tabRef: HTMLDivElement | View | null; containerRef: React.RefObject; containerX: number; containerWidth: number; From e65702464f4b5e97f2691fd5935b863131e066dc Mon Sep 17 00:00:00 2001 From: GCyganek Date: Fri, 13 Mar 2026 11:47:59 +0100 Subject: [PATCH 3/3] Fix lint --- src/components/TabSelector/TabSelectorContext.tsx | 2 +- src/components/TabSelector/TabSelectorItem.tsx | 1 - src/components/TabSelector/scrollToTab/types.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/TabSelector/TabSelectorContext.tsx b/src/components/TabSelector/TabSelectorContext.tsx index dcedf2951aea4..adff7d56693a0 100644 --- a/src/components/TabSelector/TabSelectorContext.tsx +++ b/src/components/TabSelector/TabSelectorContext.tsx @@ -1,6 +1,6 @@ import React, {createContext, useRef} from 'react'; // eslint-disable-next-line no-restricted-imports -import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, ScrollView as RNScrollView, View} from 'react-native'; +import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, ScrollView as RNScrollView} from 'react-native'; import scrollToTabUtil from './scrollToTab'; type TabSelectorContextValue = { diff --git a/src/components/TabSelector/TabSelectorItem.tsx b/src/components/TabSelector/TabSelectorItem.tsx index eef4faba7eda3..38050d80a54c8 100644 --- a/src/components/TabSelector/TabSelectorItem.tsx +++ b/src/components/TabSelector/TabSelectorItem.tsx @@ -1,7 +1,6 @@ import React, {useContext, useState} from 'react'; // eslint-disable-next-line no-restricted-imports import {Animated} from 'react-native'; -import type {View} from 'react-native'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Tooltip from '@components/Tooltip'; import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; diff --git a/src/components/TabSelector/scrollToTab/types.ts b/src/components/TabSelector/scrollToTab/types.ts index 23dafe994ca0c..d170ee0a1ea26 100644 --- a/src/components/TabSelector/scrollToTab/types.ts +++ b/src/components/TabSelector/scrollToTab/types.ts @@ -1,5 +1,5 @@ // eslint-disable-next-line no-restricted-imports -import type {ScrollView as RNScrollView, View} from 'react-native'; +import type {ScrollView as RNScrollView} from 'react-native'; type ScrollToTabProps = { animated?: boolean;