oblador / react-native-collapsible

Animated collapsible component for React Native, good for accordions, toggles etc
MIT License
2.47k stars 452 forks source link

Recalculate collapsible content height issue #420

Open joseortiz9 opened 3 years ago

joseortiz9 commented 3 years ago

Issue

Dynamic content inside the collapsible component have a wrong behavior updating the height, it takes an offset and sometimes just cut to the middle the content, happens always on android and sometimes on IOS.

Expected behavior is to maintain the position of the collapsible and not take offsets, dont cut the content or change the height more than expected.

I tried putting the header and collapsible inside a View but still doesnt work. Help me out here please.

libraries versions

"native-base": "^2.15.2",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "0.64.3",
"react-native-collapsible": "^1.6.0",

code

CollapsibleItem.jsx
<>
        <TitleComp
            handleCollapsible={() => setCollapsed(!collapsed)}
            {...otherProps, containerStyle}
        />
        <Collapsible collapsed={collapsed} align="center" style={{width: "100%", overflow: "visible"}}>
            <Card style={{width: "100%", ...styles}}>
                <CardItem style={[{..styles}, contentStyle]}>
                    {children}
                </CardItem>
            </Card>
        </Collapsible>
</>

ExampleDynamicContent.jsx
<CollapsibleItem
    containerStyle={{marginTop: 16}}
    contentStyle={{flexDirection: "column"}}
    >
        <Touchable onPress={selectItem}/>
         <View style={{width: '100%', marginBottom: 10, flexDirection: 'row', flexWrap: 'wrap'}}>
              {owners.map(chef =>
                  <GoldenItem key={chef.id} text={chef.fullName} onDelete={() => deleteItem(chef)} />)}
         </View>
</CollapsibleItem>

Behavior on Android

https://user-images.githubusercontent.com/26872450/142012335-7dbf5edf-c7f6-4df6-b038-a090cf4094a6.mp4

Expected behavior

https://user-images.githubusercontent.com/26872450/142011304-49ad0c0a-265e-470c-aa2c-3b5f25f4fcfa.mp4

rajsawhoney commented 2 years ago

Experienced the same issue.

oussamanm commented 2 years ago

Any Solution , i spend many hours on it

oussamanm commented 2 years ago

💯 i solved that by Calculate the Height the Child view of Collapsible, and create a new state that will hold that height to use it in attribute height of Collapsible Component.

 const [calculatedHieght, setCalculatedHieght] = useState();

declare an event that will Calculate the hieght :

    const onLayout=(event)=> {
        const {x, y, height, width} = event.nativeEvent.layout;
        console.log("~~~~~~ height = ", height);
        setCalculatedHieght(height + 20)
    }

use our state that hold the calculated hieght :

<Collapsible collapsed={!variantes[0].collapsed} style={{width: '100%', height: calculatedHieght}} >

and also set onLayout event in the child view of Collapsible component :

<View style={{width: '100%', padding: 7,}} onLayout={onLayout}>
oussamanm commented 2 years ago

But if you have Many Collapsible View at same page , you will see a slow motion while the view Collapsing

AbidKhairyAK commented 2 years ago

i solved this by forcing react to re-render the whole element by putting dependencies on key attributes. eg:

<Collapsible collapsed={isCollapsed} key={searchString}>

if it doesn't work then try to put key attribute on the parent element

arshiyanaz1 commented 2 years ago

@oussamanm can you please describe it in detail, as i tried your method but i guess i am doing it wrong.please share your example with code.

FrontendTerminator commented 2 years ago

i solved this by forcing react to re-render the whole element by putting dependencies on key attributes. eg:

<Collapsible collapsed={isCollapsed} key={searchString}>

if it doesn't work then try to put key attribute on the parent element

It works with key but without animation on ios

gregbrinker commented 2 years ago

This took me a while to fully figure out. I tried many different things, and finally found what I believe to be the best solution for this. Hopefully this helps people in the future.

The idea here is to wrap the children in a single view. onLayout of that view, set a currentHeight state. We ignore when the height is 0 - all that means is the cell was collapsed, the children height didn't actually change. If we don't ignore 0, the collapsing animation will not work when expanding.

The currentHeight should then be used as the `' key. This will trigger re-renders properly.


const [currentHeight, setCurrentHeight] = useState<number>(0)

const onLayout = event => {
    const newHeight = event.nativeEvent.layout.height

    // Don't do anything for height = 0 (collapsed state)
    if (newHeight === 0) return

    // Only toggle key if the height has changed
    if (newHeight !== currentHeight) {
        setCurrentHeight(newHeight)
    }
}

// Use currentHeight state as the key
<Collapsible collapsed={collapsed} key={currentHeight} renderChildrenCollapsed={true}>
    <View onLayout={onLayout}>
        {children}
    </View>
</Collapsible>
DiorAbjalilov commented 1 year ago

https://github.com/oblador/react-native-collapsible/assets/78947884/f55cbfec-974a-4003-9bf2-2f996d361032

<View>
                <Collapsible
                  duration={1000}
                  collapsed={isCollapsed}
                  collapsedHeight={100}>
                  <HTMLRender source={someText} />
                </Collapsible>
 </View>

error for only Android, help me 🤦‍♂️

ddnazzah commented 6 months ago

This took me a while to fully figure out. I tried many different things, and finally found what I believe to be the best solution for this. Hopefully this helps people in the future.

The idea here is to wrap the children in a single view. onLayout of that view, set a currentHeight state. We ignore when the height is 0 - all that means is the cell was collapsed, the children height didn't actually change. If we don't ignore 0, the collapsing animation will not work when expanding.

The currentHeight should then be used as the `' key. This will trigger re-renders properly.

const [currentHeight, setCurrentHeight] = useState<number>(0)

const onLayout = event => {
    const newHeight = event.nativeEvent.layout.height

    // Don't do anything for height = 0 (collapsed state)
    if (newHeight === 0) return

    // Only toggle key if the height has changed
    if (newHeight !== currentHeight) {
        setCurrentHeight(newHeight)
    }
}

// Use currentHeight state as the key
<Collapsible collapsed={collapsed} key={currentHeight} renderChildrenCollapsed={true}>
    <View onLayout={onLayout}>
        {children}
    </View>
</Collapsible>

You can also stringify what ever value that is dynamic in the collapsible and set it as the key.