Background
Using React Native I was able to make collapsible card component. On Icon click the card slides up hiding its content, or expands showing
Made a brand new one for you. Simple and works fine.
Note: no state
required for this component. fewer state, better performance.
Maybe you could modify your own style on top of this =)
class Card extends Component {
anime = {
height: new Animated.Value(),
expanded: false,
contentHeight: 0,
}
constructor(props) {
super(props);
this._initContentHeight = this._initContentHeight.bind(this);
this.toggle = this.toggle.bind(this);
this.anime.expanded = props.expanded;
}
_initContentHeight(evt) {
if (this.anime.contentHeight>0) return;
this.anime.contentHeight = evt.nativeEvent.layout.height;
this.anime.height.setValue(this.anime.expanded ? this._getMaxValue() : this._getMinValue() );
}
_getMaxValue() { return this.anime.contentHeight };
_getMinValue() { return 0 };
toggle() {
Animated.timing(this.anime.height, {
toValue: this.anime.expanded ? this._getMinValue() : this._getMaxValue(),
duration: 300,
}).start();
this.anime.expanded = !this.anime.expanded;
}
render() {
return (
<View style={styles.titleContainer}>
<View style={styles.title}>
<TouchableHighlight underlayColor="transparent" onPress={this.toggle}>
<Text>{this.props.title}</Text>
</TouchableHighlight>
</View>
<Animated.View style={[styles.content, { height: this.anime.height }]} onLayout={this._initContentHeight}>
{this.props.children}
</Animated.View>
</View>
);
}
}
Usage:
<Card title='Customized Card 1' expanded={false}>
<Text>Hello, this is first line.</Text>
<Text>Hello, this is second line.</Text>
<Text>Hello, this is third line.</Text>
</Card>
Visual result: (only second card start with expanded={true}
, others with expanded={false}
)
The expanded value 'true/false' should be passed from the calling component and you need to use this value to expand/collapse your component. I can't see your have used 'expanded' inside render method. You should do like this:
{ this.props.expanded && <View style={styles.card} onLayout={this._setMaxHeight.bind(this)}>
{this.props.children}
</View> }
use react-native-collapsible-view.
you can pass it expanded
property which allows you to fully control the collapsible state by other components.
you can also leave him self-controlled but pass initExpanded
which allows you to choose what is the initial state of the collapsible.
i follow step on some blog then i have same condition with it. so i made a changes after clue from others here. my post maybe is to late. but i wish will be help others.
import React from 'react';
import {
Text,
View,
TouchableOpacity,
Image,
Animated,
StyleSheet
} from 'react-native'
import styles from './styles' //put styles from another file
class PanelExpanding extends Component{
constructor(props) {
super(props)
this.state = {
expanded: false, //step 1
animation: new Animated.Value()
}
this.icons = {
'up': require('../../..assets/images/up.png'),
'down': require('../../../assets/images/down.png')
}
}
toggle(){
let finalValue = this.state.expanded? this.state.maxHeight + this.state.minHeight : this.state.minHeight,
initialValue = this.state.expanded? this.state.minHeight : this.state.maxHeight + this.state.minHeight;
//step 2, this needed, if we use !this.state.expanded i don't know how it wont changes at first
if(this.state.expanded === true){
return this.setState({ expanded : false })
} else {
return this.setState({ expanded : true })
}
this.state.animation.setValue(initialValue);
Animated.spring(
this.state.animation,
{ toValue: finalValue }
).start();
}
_setMinHeight(event){
this.setState({
minHeight : event.nativeEvent.layout.height
});
}
_setMaxHeight(event){
this.setState({
maxHeight : event.nativeEvent.layout.height
});
}
render(){
let icon = this.icons['down'],
textSwap = 'SHOWMORE'
if(this.state.expanded){
icon = this.icons['up'],
textSwap = 'SHOWLESS'
}
return (
<Animated.View
style={[styles.containerPanel,{height: this.state.animation}]} >
<View>
<View style={styles.titleContainer} onLayout={this._setMinHeight.bind(this)}>
<TouchableOpacity
style={styles.button}
onPress={this.toggle.bind(this)} >
<Text style={styles.textShow}>{textSwap}</Text>
<Image
style={styles.buttonImage}
source={icon} />
</TouchableOpacity>
</View>
//step 3, add this to initial when first render is false
{this.state.expanded && <View style={styles.body} onLayout={this._setMaxHeight.bind(this)}>
<Text>
{this.props.children}
</Text>
</View>}
</View>
</Animated.View>
)
}
}
export default PanelExpanding
// With hooks for rn
import React, { useState, useEffect } from 'react'
import {
Text,
View,
TouchableOpacity,
Image,
StyleSheet
} from 'react-native'
import styles from '../../assets/styles'
const usePanelExpanding = (props) => {
let icons = {
'up': require('../../images/formicons/icons/close.png'),
'down': require('../../images/formicons/icons/disclosure.png')
}
const [expanded, setExpanded] = useState(false);
const [iconShow, setIconShow] = useState('up');
const [textSwap, setTextSwap] = useState('Show more...');
useEffect(
() => {
expanded ? setIconShow('up') : setIconShow('down')
expanded ? setTextSwap('Show less') : setTextSwap('Show more ...')
},
[expanded]
);
const toggle = () =>{
setExpanded(!expanded)
}
const panel = (<View>
<View style={styles.titleContainer} >
<TouchableOpacity
style={styles.button}
onPress={toggle} >
<Image
style={styles.buttonImage}
source={icons[iconShow]} />
</TouchableOpacity>
</View>
{expanded && <View style={styles.mainPageFull} >
{props}
</View>
}
</View>)
return {
panel,
toggle
}
}
export default usePanelExpanding
// How to use it from a parent function
const expandingBioObject = usePanelExpanding(<Text>{profile.bio}</Text>)
<Card title="Biography" containerStyle={styles.CardContainerBio}>
{expandingBioObject.panel}
</Card>