From 75c6e3a0190428140d3faaaf07c95fb53cb091cf Mon Sep 17 00:00:00 2001 From: Andrei Alecu Date: Fri, 5 Apr 2024 13:06:14 +0300 Subject: [PATCH] fix: dynamic tab sync --- example/src/ConditionalTabs.tsx | 3 +-- example/src/DynamicTabs.tsx | 7 ++--- src/Container.tsx | 23 +++++++++------- src/Lazy.tsx | 23 +++++++++------- src/hooks.tsx | 48 +++++++-------------------------- src/types.ts | 2 +- yarn.lock | 8 +++--- 7 files changed, 46 insertions(+), 68 deletions(-) diff --git a/example/src/ConditionalTabs.tsx b/example/src/ConditionalTabs.tsx index 4c8da520..620d8216 100644 --- a/example/src/ConditionalTabs.tsx +++ b/example/src/ConditionalTabs.tsx @@ -1,6 +1,5 @@ import React, { useState } from 'react' -import { View, Text, StyleSheet } from 'react-native' -import { TouchableOpacity } from 'react-native-gesture-handler' +import { View, Text, StyleSheet, TouchableOpacity } from 'react-native' import ExampleComponent from './Shared/ExampleComponent' import { ExampleComponentType } from './types' diff --git a/example/src/DynamicTabs.tsx b/example/src/DynamicTabs.tsx index de94b922..f4970872 100644 --- a/example/src/DynamicTabs.tsx +++ b/example/src/DynamicTabs.tsx @@ -1,7 +1,6 @@ import React from 'react' -import { Text, View, StyleSheet } from 'react-native' +import { Text, View, StyleSheet, TouchableOpacity } from 'react-native' import * as Tabs from 'react-native-collapsible-tab-view' -import { TouchableOpacity } from 'react-native-gesture-handler' import { AlbumsContent } from './Shared/Albums' import { ArticleContent } from './Shared/Article' @@ -75,7 +74,9 @@ const DynamicTabs: ExampleComponentType = () => { } const TabBarComponent = React.useCallback( - (props) => , + (props: Tabs.MaterialTabBarProps) => ( + + ), [] ) diff --git a/src/Container.tsx b/src/Container.tsx index dc8eb652..913baa30 100644 --- a/src/Container.tsx +++ b/src/Container.tsx @@ -120,7 +120,7 @@ export const Container = React.memo( const accDiffClamp: ContextType['accDiffClamp'] = useSharedValue(0) const scrollYCurrent: ContextType['scrollYCurrent'] = useSharedValue(0) const scrollY: ContextType['scrollY'] = useSharedValue( - tabNamesArray.map(() => 0) + Object.fromEntries(tabNamesArray.map((n) => [n, 0])) ) const contentHeights: ContextType['contentHeights'] = useSharedValue( @@ -137,12 +137,6 @@ export const Container = React.memo( : 0 ) - const [data, setData] = React.useState(tabNamesArray) - - React.useEffect(() => { - setData(tabNamesArray) - }, [tabNamesArray]) - const focusedTab: ContextType['focusedTab'] = useDerivedValue(() => { return tabNames.value[index.value] @@ -226,7 +220,9 @@ export const Container = React.memo( (i) => { if (i !== index.value) { offset.value = - scrollY.value[index.value] - scrollY.value[i] + offset.value + scrollY.value[tabNames.value[index.value]] - + scrollY.value[tabNames.value[i]] + + offset.value runOnJS(propagateTabChange)({ prevIndex: index.value, index: i, @@ -234,7 +230,12 @@ export const Container = React.memo( tabName: tabNames.value[i], }) index.value = i - scrollYCurrent.value = scrollY.value[index.value] || 0 + if ( + typeof scrollY.value[tabNames.value[index.value]] === 'number' + ) { + scrollYCurrent.value = + scrollY.value[tabNames.value[index.value]] || 0 + } } }, [] @@ -433,13 +434,15 @@ export const Container = React.memo( {...pagerProps} style={[pagerProps?.style, StyleSheet.absoluteFill]} > - {data.map((tabName, i) => { + {tabNamesArray.map((tabName, i) => { return ( { React.Children.toArray(children)[ diff --git a/src/Lazy.tsx b/src/Lazy.tsx index 80ca891b..576bf706 100644 --- a/src/Lazy.tsx +++ b/src/Lazy.tsx @@ -68,14 +68,17 @@ export const Lazy: React.FC<{ } }, []) - const startMountTimer = React.useCallback(() => { - // wait the scene to be at least mountDelay ms focused, before mounting - setTimeout(() => { - if (focusedTab.value === name) { - if (isSelfMounted.current) setCanMount(true) - } - }, mountDelayMs) - }, [focusedTab.value, mountDelayMs, name]) + const startMountTimer = React.useCallback( + (focusedTab: string) => { + // wait the scene to be at least mountDelay ms focused, before mounting + setTimeout(() => { + if (focusedTab === name) { + if (isSelfMounted.current) setCanMount(true) + } + }, mountDelayMs) + }, + [mountDelayMs, name] + ) useAnimatedReaction( () => { @@ -87,7 +90,7 @@ export const Lazy: React.FC<{ opacity.value = 1 runOnJS(setCanMount)(true) } else { - runOnJS(startMountTimer)() + runOnJS(startMountTimer)(focusedTab.value) } } }, @@ -116,7 +119,7 @@ export const Lazy: React.FC<{ return { opacity: opacity.value, } - }, []) + }, [opacity]) const onLayout = useCallback(() => { didTriggerLayout.value = true diff --git a/src/hooks.tsx b/src/hooks.tsx index 34785a5c..de81e5c1 100644 --- a/src/hooks.tsx +++ b/src/hooks.tsx @@ -249,7 +249,6 @@ export const useScrollHandlerY = (name: TabName) => { revealHeaderOnScroll, refMap, tabNames, - index, headerHeight, contentInset, containerHeight, @@ -275,19 +274,13 @@ export const useScrollHandlerY = (name: TabName) => { enabled.value = toggle if (toggle) { - const tabIndex = tabNames.value.findIndex((n) => n === name) - const ref = refMap[name] - scrollTo( - ref, - 0, - scrollY.value[tabIndex], - false, - `[${name}] restore scroll position - enable` - ) + const y = scrollY.value[name] ?? scrollYCurrent.value + + scrollTo(ref, 0, y, false, `[${name}] restore scroll position - enable`) } }, - [enabled, name, refMap, scrollTo, scrollY.value, tabNames.value] + [enabled, name, refMap, scrollTo, scrollY.value, scrollYCurrent.value] ) /** @@ -399,9 +392,9 @@ export const useScrollHandlerY = (name: TabName) => { scrollYCurrent.value = y } - scrollY.value[index.value] = scrollYCurrent.value + scrollY.value[name] = scrollYCurrent.value oldAccScrollY.value = accScrollY.value - accScrollY.value = scrollY.value[index.value] + offset.value + accScrollY.value = scrollY.value[name] + offset.value if (revealHeaderOnScroll) { const delta = accScrollY.value - oldAccScrollY.value @@ -485,8 +478,8 @@ export const useScrollHandlerY = (name: TabName) => { focusedTab.value !== name ) { let nextPosition: number | null = null - const focusedScrollY = scrollY.value[Math.round(indexDecimal.value)] - const tabScrollY = scrollY.value[tabIndex] + const focusedScrollY = scrollY.value[focusedTab.value] + const tabScrollY = scrollY.value[name] const areEqual = focusedScrollY === tabScrollY if (!areEqual) { @@ -514,7 +507,7 @@ export const useScrollHandlerY = (name: TabName) => { if (nextPosition !== null) { // console.log(`sync ${name} ${nextPosition}`) - scrollY.value[tabIndex] = nextPosition + scrollY.value[name] = nextPosition scrollTo(refMap[name], 0, nextPosition, false, `[${name}] sync pane`) } } @@ -560,21 +553,9 @@ export function useAfterMountEffect( nextOnLayout: ViewProps['onLayout'], effect: React.EffectCallback ) { - const name = useTabNameContext() - const { - //tabsMounted, - refMap, - scrollY, - //scrollYCurrent, - tabNames, - } = useTabsContext() - const didExecute = useRef(false) const didMount = useSharedValue(false) - const scrollTo = useScroller() - const ref = name ? refMap[name] : null - useAnimatedReaction( () => { return didMount.value @@ -582,16 +563,7 @@ export function useAfterMountEffect( (didMount, prevDidMount) => { if (didMount && !prevDidMount) { if (didExecute.current) return - if (ref) { - const tabIndex = tabNames.value.findIndex((n) => n === name) - scrollTo( - ref, - 0, - scrollY.value[tabIndex], - false, - `[${name}] restore scroll position` - ) - } + effect() didExecute.current = true } diff --git a/src/types.ts b/src/types.ts index bab8700e..2bb38d46 100644 --- a/src/types.ts +++ b/src/types.ts @@ -168,7 +168,7 @@ export type ContextType = { /** * Array of the scroll y position of each tab. */ - scrollY: SharedValue + scrollY: SharedValue> containerHeight: SharedValue /** * Object containing the ref of each scrollable component. diff --git a/yarn.lock b/yarn.lock index b09fdf52..8d90433e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6925,10 +6925,10 @@ human-signals@^5.0.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== -husky@7: - version "7.0.4" - resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" - integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== +husky@^9.0.11: + version "9.0.11" + resolved "https://registry.yarnpkg.com/husky/-/husky-9.0.11.tgz#fc91df4c756050de41b3e478b2158b87c1e79af9" + integrity sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw== iconv-lite@^0.4.24: version "0.4.24"