why i got [object object ] - reactjs

import React, { Component } from "react";
import { Button, View ,Text ,StyleSheet, FlatList, ScrollView} from "react-native";
import DateTimePicker from "react-native-modal-datetime-picker";
import moment from 'moment'
import addDays from 'date-fns/addDays'
import Modal from 'react-native-modal';
export default class MExample extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
day:[],
isDateTimePickerVisible: false,
choseDate:'',
visibleModal: false,
lists:''
};
}
showDateTimePicker = () => {
this.setState({ isDateTimePickerVisible: true });
};
hideDateTimePicker = () => {
this.setState({ isDateTimePickerVisible: false });
};
handleDatePicker = ( date ) => {
// console.log("A date has been picked:", date); here date come correctly
this.setState ({
choseDate: 'Subscription start date ' + moment(date).format('MMMM, Do YYYY '),
})
this.hideDateTimePicker();
};
hideListPicker = () => {
this.setState({ visibleModal: null ,list:[] });
};
handleListPicker = ( list ) => {
console.log(list.toString())
this.setState ({
lists: 'list of start dates ' + list
})
this.hideListPicker();
};
getListViewItem = (item) => {
let newList = this.state.list;
if (newList.includes(item)) {
let index = newList.indexOf(item);
newList.splice(index,1);
} else {
newList.push(item);
}
this.setState({
list: newList,
});
}
renderModalContent = () => (
<View>
<Text style={styles.textBox} onPress={this.showDateTimePicker}>Select Date</Text>
<DateTimePicker
isVisible={this.state.isDateTimePickerVisible}
onConfirm={this.handleDatePicker}
onCancel={this.hideDateTimePicker}
minimumDate = {new Date()}
maximumDate = {addDays(new Date(), 30)}
/>
<View style = {{backgroundColor:'white'}}>
<View>
<FlatList horizontal={true}
data = {[{day: '1'},{day: '2'}, {day: '3'},{day: '4'}, {day: '5'},{day: '6'},{day: '7'}]}
renderItem={({item, index}) =>
<Text style={styles.textBox} key={index}
onPress={this.getListViewItem.bind(this, item.day)}>{item.day}
</Text>}
/>
<ScrollView
style = {{marginHorizontal: 20}}
horizontal={true}
>
{
this.state.list.map((l, index) => {
return(
index !== this.state.list.length - 1 ? <Text style={{fontSize:30, color:'red'}}>{l}, </Text> : <Text style={{fontSize:30, color:'red'}}>{l}</Text>
)
})
}
</ScrollView>
</View>
</View>
<Button
onPress={this.handleListPicker}
title="Submit"
/>
</View>
);
render() {
return (
<>
<Text style={{fontSize:20}}>Frequency</Text>
<View style={styles.container} >
<Text style={styles.textBox} onPress={() => this.setState({ visibleModal: 'default' })}>Weekly </Text>
</View>
<Text style={{fontSize:20, color:'black', textAlign:'center'}}>{this.state.choseDate} </Text>
<Text style={{fontSize:20, color:'black', textAlign:'center'}}>{this.state.lists} </Text>
<Modal isVisible={this.state.visibleModal === 'default'}
onBackButtonPress={() => this.setState({ visibleModal: null, list:[] }, )}>
{this.renderModalContent()}
</Modal>
</>
);
}
}
const styles = StyleSheet.create({
container:{
flexDirection:'row',
flexWrap:'wrap',
justifyContent:'center',
},
textBox :{
fontSize:20,
textAlign:'center',
borderWidth: 1,
borderRadius: 12,
borderColor: "#CBCBCB",
margin:10,
padding:5,
backgroundColor:'#a0a3a3'
},
});
i have created modal here user select date list and after submit i clear list in setState
why i get [object object] in console
export default class MExample extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
visibleModal: false,
lists: ''
};
}
hideListPicker = () => {
this.setState({ visibleModal: null ,list:[] });
};
handleListPicker = ( list ) => {
console.log(list.toString())
// [object objcet]
this.setState ({
lists: 'list of start dates ' + list
})
this.hideListPicker();
};
render(){
return(
// jsx <Text>{this.state.lists} </Text> // [object object]
<Button onPress={this.handleListPicker}
title="Submit"
/>
)
}

Use JSON.stringify instead .toString
This question might help you What's the difference in using toString() compared to JSON.stringify()? understand the difference
let x = {a: 123, b: 321};
console.log("toString", x.toString());
console.log("stringified", JSON.stringify(x));
If you're having a circular JSON you might like to visit How can I print a circular structure in a JSON-like format? to see how to console such JSONs

It is because you are passing event as parameter on Button click, which is an object. Now when you use list.toString(), it will convert this event Object into String and show you [Object Object].
You can verify it by using
console.log("data = ",list)
instead of
console.log(list.toString())

In your case, just remove the toString() function to get what you need.
SIDE NOTE: When use console.log in browser environment, when your object has nested levels, you might get something like
> a: [...]
After clicking > in the web console, you will see the value, but this value is determined at the moment you clicked, not the moment it ran through your console.log.
The best way to get the value at the time you logged it, use console.log(JSON.stringify(object)) instead.
To format the JSON output, passing params to stringify function like below:
console.log(
JSON.stringify
(
obj, // the object you want to log
null,
2 // the indent counts by spaces for each level
)
)

Because method toString returns it. For print object data just pass object to console.log method like console.log(list)

Related

redux-saga used with redux causes render() to be called twice

I have a problem while incorporating the redux saga in my application .
What I understand from the tutorials is that the middleware will parse the action dispatched from the app and will process some async operations(asycn storage, api).
Then put another action in the pipeline which will trigget the reducer and then updates the state.
Flow is that my component button click triggers an action dispatch which is caught by the watcher in the saga, and then api calls are processed then the put is done to send data to reducer to update the state.
If in my component I dispatch the action FETCH_DATA, the saga is catching that and processing the data fetch and then calling the updateprofile. This calls the reducer and processing happens.
This is what I expected. But even before hitting the saga FETCH_DATA, the call comes to the reducer and since there is no action type FETCH_DATA case to handle it will return the default state and thiscauses the app to re render. So rendering happens twice and it is causing some problem to my items in the list.
I think this is also somewhat expected as I read in some article .How to get rid of this re rendering ?
function* datafetch(action) {
let { data } = yield call(loginApi, action.payload);
yield put(updateProfile(data.profile));
}
export function* dataFetcherSaga() {
yield takeLatest('FETCH_DATA', datafetch);
}
/reducer.js
const toDoListReducer = (state, action) => {
switch (action.type) {
case "UPDATE_APPREDUX_STATE":
return {
//some processing with data and state
};
break;
case "UPDATE_PROFILE":
return {
//some processing with data and state
};
break;
default:
return state;
}
return state;
};
export default toDoListReducer;
//action
export const fetchData = currentDay => {
return {
type: 'FETCH_DATA',
currentDay: currentDay
};
};
export function updateProfile(profile) {
return { type: 'UPDATE_PROFILE', payload: authParams };
}
//component
render(){
return (
<View style={styles.viewStyle}>
<SafeAreaView>
<View style={styles.viewPadding}>
<View>
<View style={styles.toDoViewStyle}>
<TextInput
style={styles.toDoInputStyle}
placeholder="What you gonna do ?"
onChangeText={text => {
this.newTask = text;
}}
/>
<TouchableOpacity
onPress={() => {
this.props.updateTasks(
{
taskID: new Date().getTime(),
taskDay: this.currentDay, //millisecond field to get a unique value
taskValue: this.newTask,
taskCompleted: false,
taskCompletedTime: null
},
"addTask"
);
}}
>
<Image
style={styles.addImage}
source={require("../../assets/add.png")}
/>
</TouchableOpacity>
</View>
<Text>
↓ To Do Items ↓
</Text>
<SectionList
style={styles.flatListStyle}
renderItem={({ item }) => <ToDoListItem value={item} />}
renderSectionHeader={({ section: { title, data } }) => {
if (data.length > 0) {
return (
<Text
style={{
paddingTop: 5,
fontWeight: "bold",
fontStyle: "italic",
fontSize: 15,
color: title === "Completed Tasks:" ? "green" : "red",
textDecorationLine: "underline"
}}
>
{title}
</Text>
);
}
}}
stickySectionHeadersEnabled={false}
sections={[
{
title: "Completed Tasks:",
data: this.props.tasks.filter(tasks => {
return tasks.taskCompleted === true;
})
},
{
title: "InComplete Tasks:",
data: this.props.tasks.filter(tasks => {
return tasks.taskCompleted === false;
})
},
,
]}
keyExtractor={(item, index) => item + index}
/>
</View>
</View>
</SafeAreaView>
</View>
);}
//child item
class ToDoListItem extends React.Component {
constructor(props) {
super(props);
//this.state = { checked: false };
}
selectItem = () => {
let updatedObject = {
taskID: this.props.value.taskID,
taskCompleted: !this.props.value.taskCompleted,
};
this.props.done(updatedObject);
};
deleteItem = () => {
let deletedObject = {
taskID: this.props.value.taskID,
};
this.props.delete(deletedObject);
};
render() {
return (
<View style={styles.viewStyle}>
<View style={styles.checkBoxStyle}>
<CheckBox
checkedCheckBoxColor="green"
onClick={this.selectItem}
isChecked={this.props.value.taskCompleted}/>
</View>
<View style={styles.inputTextViewStyle}>
<Text
style={
this.props.value.taskCompleted
? styles.completeDone
: styles.inComplete
}>
{this.props.value.taskValue}
</Text>
{this.props.value.taskCompleted && <Text style={{ fontSize: 11, fontStyle: "italic", color: "blue" }}>{"\n"}{"Completed # " + this.props.value.taskCompletedTime}</Text>}
</View>
<View style={styles.deleteTextStyle}>
<TouchableOpacity onPress={this.deleteItem}>
<Image
style={styles.deleteImage}
source={require('../../assets/delete.png')}
/>
</TouchableOpacity>
</View>
</View>
);
}
}
Yes this is expected behavior when you dispatch any action it will go to reducer first instead of any middleware. Best practice is to show the loading screen as mentioned but if you really want to hold your render then you can use shouldComponentUpdate() Component Lifecycle Method which takes two params nextProps and nextState. This method basically hold the re-render which can be achieved by a simple if condition for e.g.
shouldComponentUpdate (nextProps, nextState) {
let shouldUpdate = true
if ((nextProps.someProps === this.props.someProps )) {
shouldUpdate = false
}
return shouldUpdate
}
To prevent re-rendering, you could create a container for the component. In this container component, use the redux compose method and connect method from react-redux, e.g
const ContainerComponent = compose(
connect(mapStateToProps),
component-that-displays-on-loading
)(Component-that-needs-data-from-the-state);
compose is a higher order component that runs passed-in functions from right-to-left. This pattern skips the first render and executes the flow as expected. Hope this helps.

Remove one item from flatlist

I'm using FlatList to show a list of data.
I was trying dozens of example how to remove one row from data, but couldn't find the right solution.
Right now I'm removing all data from state, but I want to remove just one item.
Here is my HomeScreen which displays list of data:
class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
data: data.products
};
}
static navigationOptions = {
title: "Products"
};
keyExtractor = (item, index) => item.id;
openDetails = data => {
this.props.navigation.navigate("Details", {
data
});
};
deleteItem = data => {
this.setState({ data: ''})
}
renderProduct = ({ item, index }) => {
return (
<Item
itemTitle={item.title}
openDetails={() => this.openDetails(item)}
itemUrl={item.imageUrl}
data={this.state.data}
deleteItem={() => this.deleteItem(item)}
/>
);
};
render() {
return (
<FlatList
data={this.state.data}
renderItem={this.renderProduct}
keyExtractor={this.keyExtractor}
/>
);
}
}
export default HomeScreen;
Here is my Item component which is showing one item and receiving deleteRow function as prop:
const Item = props => {
return (
<View>
<TouchableOpacity onPress={props.deleteItem}>
<Image
source={{ uri: props.itemUrl }}
style={{ width: "100%", height: 220 }}
/>
<Text style={styles.productTitle}>{props.itemTitle}</Text>
</TouchableOpacity>
</View>
);
};
export default Item;
Use below deleteItem function.
deleteItem = data => {
let allItems = [...this.state.data];
let filteredItems = allItems.filter(item => item.id != data.id);
this.setState({ data: filteredItems })
}
This should filter out the deleted item.

Object array data source for FlatList empties when used as renderItem

this is an extension of an earlier question now debugged to realize it's a different issue. I have an object array that looks like this when logged to console:
I now want to use this object array to display a list using FlatList component. in my state constructor, I set a variable to itemList which takes in objects generated from my listenForMusic function using this.setState():
class VideoFeed extends React.Component {
constructor(props) {
super(props);
//this.dataRef = database.ref("music");
this.state = {
itemList: null,
}
}
componentWillMount() {
this.listenForMusic();
}
listenForMusic(){
var dataRef = database.ref("music");
let items = [];
dataRef.orderByChild("date").on('child_added', (snap) => {
items.push({
videoURL: snap.val().youtubeURL,
title: snap.val().title,
thumbnail: snap.val().thumbnail
});
});
this.setState({ itemList: items })
}
render() {
console.log(this.state.itemList);
return (
<View>
<FlatList
data={this.state.itemList}
renderItem={({item}) => { console.log(item); return (<Text>{item.videoURL}</Text>) }}
/>
</View>
);
}
}
I have that console.log in my render function and I see the image I posted above, but when I try and console.log the item in the renderItem, it does not show anything in the console (not even an empty array). Where did my data go that I submitted into the data prop?
Should be obvious, but nothing is printed in that <Text> tag.
EDIT: Whole class added
class VideoFeed extends React.Component {
constructor(props) {
super(props);
//this.dataRef = database.ref("music");
this.state = {
itemList: null,
}
//this.listenForMusic = this.listenForMusic.bind(this);
}
componentWillMount() {
this.listenForMusic();
}
listenForMusic(){
var dataRef = database.ref("music");
let items = [];
dataRef.orderByChild("date").on('child_added', (snap) => {
items.push({
videoURL: snap.val().youtubeURL,
title: snap.val().title,
thumbnail: snap.val().thumbnail
});
});
this.setState({ itemList: items })
}
_keyExtractor = (item, index) => item.id;
_renderVideoItem = ({item}) => (
<TouchableWithoutFeedback
onPress={Actions.Submit}
>
<View style={styles.mediaContainer}>
<Image
source={{uri: item.thumbnail }}
style={styles.mediaThumbnail}
/>
<View style={styles.mediaMetaContainer}>
<View style={styles.topMetaContainer}>
<Text style={styles.mediaTitle}>
{item.title}
</Text>
<Text style={styles.sharedByUser}>
UNCVRD
</Text>
</View>
<View style={styles.bottomMetaContainer}>
<Icon
name='youtube-play'
type='material-community'
color='#ff0000'
size={16}
/>
<View style={styles.bottomRightContainer}>
<Icon
name='thumb-up'
size={12}
color='#aaa'
/>
<Text style={styles.metaLikeCounter}>
16
</Text>
</View>
</View>
</View>
</View>
</TouchableWithoutFeedback>
);
render() {
console.log(this.state.itemList);
return (
<View>
<FlatList
data={this.state.itemList}
renderItem={({item}) => { console.log(item); return (<Text>{item.title}</Text>) }}
/>
</View>
);
}
}
EDIT 2: So I did an interesting test, I made two state variables: realList and fakeList:
state = {
realList: [],
fakeList: [],
}
Then when the page will load, the following function is run that populates arrays called real and fake. One with data pulled from Firebase, the other hardcoded with array information:
listenForMusic = () => {
var dataRef = database.ref("music");
let real = [];
let fake = [];
dataRef.orderByChild("date").on('child_added', (snap) => {
var url = snap.val().youtubeURL;
var vidTitle = snap.val().title;
var thumb = snap.val().thumbnail;
real.push({
videoURL: url,
title: vidTitle,
thumbnail: thumb
});
});
fake.push({videoURL: "https://youtu.be/AHukwv_VX9A", title: "MISSIO - Everybody Gets High (Audio)", thumbnail: "https://i.ytimg.com/vi/AHukwv_VX9A/hqdefault.jpg"}, {videoURL: "https://youtu.be/G-yWpz0xkWY", title: "SMNM - Million ft. Compulsive", thumbnail: "https://i.ytimg.com/vi/G-yWpz0xkWY/hqdefault.jpg"});
this.setState({
realList: real,
fakeList: fake
});
}
Then I console.log both of the arrays after the render function and I see this:
And opening both:
So my question is, why does the "real" array look empty but still has data populated inside while the "fake" array displays that it holds two objects inside of it, even before we take a look inside??
I think
renderItem={({item}) => return (<Text>{item.videoURL}</Text>)}
should be:
renderItem={(item) => return (<Text>{item.videoURL}</Text>)}
The way you have it now is trying to deconstruct a property called item, which doesn't exist.

Autocomplete with react native

can someone please help me debug this code, I'd like to use 'react-native-autocomplete-input' library to autocomplete a search text input, basically the api request return a list of stock symbols and company names and I'm thinking to store this file locally to make it faster, for right now I just want to make it work using fetch. The autocomplete must search in responseJson.symbol
Here's what I've done so far:
import Autocomplete from 'react-native-autocomplete-input';
import React, { Component } from 'react';
import { StyleSheet, Text, TouchableOpacity, Platform,
ScrollView, View, ActivityIndicator,
} from 'react-native';
class AutocompleteExample extends Component {
constructor(props) {
super(props);
this.state = {
symbols: [],
query: '',
};
}
searchSymbol(query) {
if (query === '') {
return [];
}
const { symbols } = this.state;
const url = `https://api.iextrading.com/1.0/ref-data/symbols`;
fetch(url)
.then((response) => response.json())
.then((responseJson) => {
this.setState({
symbols:responseJson.symbol,
});
})
.catch((error) => {
console.error(error);
});
return symbols;
}
render() {
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
const { query } = this.state;
const symbols = this.searchSymbol(query);
return (
<ScrollView>
<View style={styles.MainContainer}>
<Text style={{fontSize: 12,marginBottom:10}}> Type your favorite stock</Text>
<Autocomplete
autoCapitalize={true}
containerStyle={styles.autocompleteContainer}
data={symbols}
defaultValue={query}
onChangeText={text => this.setState({ query: text })}
placeholder="Enter symbol"
renderItem={({ symbol }) => (
<TouchableOpacity onPress={() => this.setState({ query: symbol })}>
<Text style={styles.itemText}>
{symbol}
</Text>
</TouchableOpacity>
)}
/>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
MainContainer :{
justifyContent: 'center',
flex:1,
paddingTop: (Platform.OS === 'ios') ? 20 : 20,
padding: 5,
},
autocompleteContainer: {
borderWidth: 1,
zIndex:999,
borderColor: '#87ceeb',
},
itemText: {
fontSize: 17,
color:'#000000',
}
});
module.exports = AutocompleteExample
I don't see any error on the console and the api request is working correctly, I just can't access symbols const Do I have to render something like cloneWithRows(responseJson.symbols) ? Thanks
First of all, the searchSymbol method should be either binded in the component constructor, or declared as a class property. Otherwise, "this" will not point to the component instance.
Then, it seems that your state does not have an isLoading property, but you use it in your render function.
What you should probably do is call your asynchronous searchSymbol method in the componentDidMount lifecycle method. When the promise of the fetch resolves, you should put the result in the state as well as put the isLoading boolean to false. Then your component will re-render with the now available data.

Make animated collapsible card component, with initial props to show or hide

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 its content. I would think setting the default value would be as easy as setting expanded to false or true, but I think the problem here is that when it is toggled an animation is triggered which changes the height of the card.
Example
class CardCollapsible extends Component{
constructor(props){
super(props);
this.state = {
title: props.title,
expanded: true,
animation: new Animated.Value(),
iconExpand: "keyboard-arrow-down",
};
}
_setMaxHeight(event){
this.setState({
maxHeight : event.nativeEvent.layout.height
});
}
_setMinHeight(event){
this.setState({
minHeight : event.nativeEvent.layout.height
});
this.toggle = this.toggle.bind(this);
}
toggle(){
let initialValue = this.state.expanded? this.state.maxHeight + this.state.minHeight : this.state.minHeight,
finalValue = this.state.expanded? this.state.minHeight : this.state.maxHeight + this.state.minHeight;
this.setState({
expanded : !this.state.expanded
});
if (this.state.iconExpand === "keyboard-arrow-up") {
this.setState({
iconExpand : "keyboard-arrow-down"
})
} else {
this.setState({
iconExpand : "keyboard-arrow-up"
})
}
this.state.animation.setValue(initialValue);
Animated.spring( this.state.animation, {
toValue: finalValue
}
).start();
}
render(){
return (
<Animated.View style={[styles.container,{height: this.state.animation}]}>
<View style={styles.titleContainer} onLayout={this._setMinHeight.bind(this)}>
<CardTitle>{this.state.title}</CardTitle>
<TouchableHighlight
style={styles.button}
onPress={this.toggle}
underlayColor="#f1f1f1">
<Icon
name={this.state.iconExpand}
style={{ fontSize: 30 }}/>
</TouchableHighlight>
</View>
<Separator />
<View style={styles.card} onLayout={this._setMaxHeight.bind(this)}>
{this.props.children}
</View>
</Animated.View>
);
}
}
var styles = StyleSheet.create({
container: {
backgroundColor: '#fff',
margin:10,
overflow:'hidden'
},
titleContainer: {
flexDirection: 'row'
},
card: {
padding: 10
}
});
export { CardCollapsible };
Open
Closed
Question
My goal is to allow a person calling the component to set the initial state of the component to expanded or open. But when I try changing the expanded state to false it does not render closed.
How would I go about allowing the user calling the component to select whether it is expanded or closed on initial component render?
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> }
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>
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.

Resources