PedroBern / react-native-collapsible-tab-view

A cross-platform Collapsible Tab View component for React Native
MIT License
815 stars 161 forks source link

how to fix overscroll with sticky header? #323

Closed lchenfox closed 1 year ago

lchenfox commented 1 year ago

Description

I have a page using react-navigation to navigate with the navigation bar hidden. So I'd like to custom navigation bar(the view with blue background color below). However, when I scroll up, the sticky header(the view with red background color in renderHeader) covered the custom navigation bar. It means that the sticky header shows on the navigation bar. That's not what I want. I need the navigation bar is always showing on the top of page. So, how do I fix that?

Expected behavior

The custom navigation bar is always showing even though I scroll up the flatlist with a sticky header.

Code snippets

render() {
        return  <View style={{ flex: 1 }}>
            <View style={{height: 80, backgroundColor: 'blue', justifyContent: 'center', alignItems: 'center'}}>
                <Text style={{color: 'white'}}>Custom navigation bar</Text>
            </View>
            <Tabs.Container
                renderHeader={() => <View style={{height: 200, backgroundColor: 'red'}}/>}
                headerContainerStyle={{ backgroundColor: 'transparent' }}
            >
                <Tabs.Tab name="A">
                    <Tabs.FlatList
                        data={[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}
                        renderItem={({ index }) => {
                            return (
                                <View style={[styles.box, index % 2 === 0 ? styles.boxB : styles.boxA]} />
                            )
                        }}
                        keyExtractor={v => v + ''}
                    />
                </Tabs.Tab>
            </Tabs.Container>
        </View>
}

Any answer will be appreciated! Thank U in advance.

lchenfox commented 1 year ago

Finally, I end up with nesting a ScrollView to fix the issue above like

render() {
        return  <View style={{ flex: 1 }}>
            <View style={{height: 80, backgroundColor: 'blue', justifyContent: 'center', alignItems: 'center'}}>
                <Text style={{color: 'white'}}>Custom navigation bar</Text>
            </View>
            <ScrollView contentContainerStyle={{ flexGrow: 1 }} scrollEnabled={false} horizontal={true}>
               <Tabs.Container
                  renderHeader={() => <View style={{height: 200, backgroundColor: 'red'}}/>}
                  headerContainerStyle={{ backgroundColor: 'transparent' }}
              >
                <Tabs.Tab name="A">
                    <Tabs.FlatList
                        data={[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}
                        renderItem={({ index }) => {
                            return (
                                <View style={[styles.box, index % 2 === 0 ? styles.boxB : styles.boxA]} />
                            )
                        }}
                        keyExtractor={v => v + ''}
                    />
                </Tabs.Tab>
              </Tabs.Container>
           </ScrollView>
        </View>
}

BTW, how to get the offset on scrolling up? I found the issue 265# and have a try but failed.

The code snippet


// Function component.
const TabHeader = (props) => {
    const { yOffsetCallback } = props 
    const scrollY = useCurrentTabScrollY()
    useDerivedValue(() => {
        yOffsetCallback(scrollY.value.toFixed(2));
    });
    return <View style={{height: 100}}/>;
};

// class component.

render() {
        return  <View style={{ flex: 1 }}>
            <View style={{height: 80, backgroundColor: 'blue', justifyContent: 'center', alignItems: 'center'}}>
                <Text style={{color: 'white'}}>Custom navigation bar</Text>
            </View>
            <ScrollView contentContainerStyle={{ flexGrow: 1 }} scrollEnabled={false} horizontal={true}>
               <Tabs.Container
                  renderHeader={() => <TabHeader yOffsetCallback={offsetY => console.warn("offsetY is ", offsetY)}/>}
                  headerContainerStyle={{ backgroundColor: 'transparent' }}
              >
                <Tabs.Tab name="A">
                    <Tabs.FlatList
                        data={[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}
                        renderItem={({ index }) => {
                            return (
                                <View style={[styles.box, index % 2 === 0 ? styles.boxB : styles.boxA]} />
                            )
                        }}
                        keyExtractor={v => v + ''}
                    />
                </Tabs.Tab>
              </Tabs.Container>
           </ScrollView>
        </View>
}

After doing that, an error occurred

 ERROR  Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

 ERROR  TypeError: Cannot read property 'useEffect' of null

I have to get the offset on scrolling to do some other works.

andreialecu commented 1 year ago

Take a look at the Example app in the repository and run it. I think it answers both of your questions.

Please add a video recording of the issue, hard to understand otherwise just by reading the code.

lchenfox commented 1 year ago

@andreialecu Follow the example in demo, it works. Thank U so much. Closing this issue.

bryanltobing commented 1 year ago

Follow the example in demo, it works. Thank U so much. Closing this issue.

@lchenfox can you link where you find the fix ?

lchenfox commented 1 year ago

@bryanltobing Yeah. Please see the link for more detail: https://github.com/PedroBern/react-native-collapsible-tab-view/blob/main/example/src/AnimatedHeader.tsx

Besides, please note that Animated must be imported even if you don't use it. It looks like the following:

import React from 'react'
import { StyleSheet, View } from 'react-native'
// Note: `Animated` must be imported even if don't need it.
import Animated, { useDerivedValue } from 'react-native-reanimated'
import { useCurrentTabScrollY } from '../../src/hooks'

export const Header = ({yOffsetCallback}) => {
  const scrollY = useCurrentTabScrollY()
  useDerivedValue(
    () => {
        // You can access the offset y value here. Moreover, if you want to add a callback to access 
        // the offset y in super view, you have to insure that `runOnJS` is wrapping the 
        // `yOffsetCallback`(In my project, I need to access the offset y in super view).
        const yOffset = scrollY.value.toFixed(2)
        runOnJS(yOffsetCallback)(yOffset);
    }
  )
  return <View style={{background:'red', height: 100}}/>;
}
bpeck81 commented 1 year ago

I'm somewhat new to react native and am having this issue. The example app doesnt work with the latest expo sdk. I was able to get it to work with the ScrollView solution, but I'm wondering how to get it working the correct way. Since this is the most common use case for the library, should it not be easier to use out of the box?

The header slides up and sticks to the top rather than collapses.