React Native List View Not Changing Style on State Change - reactjs

My aim is to toggle a switch component and for that change in value to affect the state of the parent which will be rendered with a different style than from the default rendered style.
At the moment the state updates fine but the component isn't re-rendered.
import React, {
AppRegistry,
Component,
Image,
ListView,
StyleSheet,
Text,
View,
Switch
} from 'react-native';
var SONGS_DATA = {
"songs" : [
{
"title" : "I Heard React Was Good",
"artist" : "Martin",
"played" : false
},
{
"title" : "Stack Overflow",
"artist" : "Martin",
"played" : false
}
]
}
class BasicSwitchExample extends Component{
constructor(props){
super(props);
this.state = {
played: false
};
this.handlePlayed = this.handlePlayed.bind(this);
}
handlePlayed(value){
console.log('handlePlayed value: ' + value);
this.setState({played: value});
this.props.callbackParent(value);
}
render() {
return <View> // this has to be on the same line or it causes an error for some reason
<Switch
onValueChange={this.handlePlayed}
style={{marginBottom: 10}}
value={this.state.played} />
</View>
}
}
class AwesomeProject extends Component {
constructor(props) {
super(props);
this.renderSong = this.renderSong.bind(this);
this.togglePlayed = this.togglePlayed.bind(this);
this.fetchData = this.fetchData.bind(this);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
loaded: false
};
}
componentDidMount() {
this.fetchData();
}
togglePlayed(value) {
// this is never reached
this.setState({played: value});
console.log('Song has been played? ' + this.state.played);
}
fetchData() {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(SONGS_DATA.songs),
loaded: true,
});
}
render() {
if (!this.state.loaded) {
return this.renderLoadingView();
}
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderSong}
style={styles.listView}
/>
);
}
renderLoadingView() {
return (
<View style={styles.container}>
<Text>
Loading songs...
</Text>
</View>
);
}
renderSong(song) {
let bgStyle = this.state.played ? styles.played : styles.container;
console.log('style : ' + bgStyle); // prints 2 for some reason
return (
<View style={this.state.played ? styles.played : styles.container}>
<View style={styles.half}>
<Text style={styles.title}>{song.title}</Text>
<Text style={styles.artist}>{song.artist}</Text>
</View>
<View style={styles.half}>
<BasicSwitchExample callbackParent={this.togglePlayed} />
</View>
</View>
);
}
}
var styles = StyleSheet.create({
container: {
/* styles here */
},
played: {
/* styles here */
},
});
AppRegistry.registerComponent('SampleApp', () => AwesomeProject);
React Native Playground
For some reason when rendering the style, it's printed out as 2. This only happens when the app is first loaded and is never reached after a switch is toggled.

You're not mutating any state that's relevant to the ListView rendering. In togglePlayed you have to change parts of the state that will change the rendered list: https://rnplay.org/apps/z0fKKA

Related

set Multiple state Id for custom component in React Native

I have implemented custom inputBox component. So When I am using this component first time then it is working fine and when I am using multiple time in one page then data is prepopulate to next component.
Custom component:
import React, { createRef } from 'react';
import {
View,
TextInput,
Alert,
Text,
StyleSheet
} from "react-native";
class BoxInput extends React.Component {
constructor(props) {
super(props)
this.state = {
digit1: '',
digit2: '',
digit3: '',
...props
}
this.digit1Ref = createRef()
this.digit2Ref = createRef()
this.digit3Ref = createRef()
}
componentDidMount() {
this.digit1Ref.current.focus()
}
componentDidUpdate(prevProps) {
if (this.state.digit1 && this.state.digit2 &&
this.state.digit3
}
saveText(text, key) {
this.setState({ ...this.state, [key]: text }, () => {
if (text) {
key == 'digit1' ? this.digit2Ref.current.focus() : null
key == 'digit2' ? this.digit3Ref.current.focus() : null
key == 'digit3'
}
const boxInputValue = this.state.digit1 + this.state.digit2 + this.state.digit3
this.props.onBoxInput(boxInputValue)
});
}
render() {
return (
<>
<TextInput maxLength={1} keyboardType={'numeric'} ref={this.digit1Ref} style={styles.boxStyle} value={this.state.digit1} onChangeText={(text) => this.saveText(text, 'digit1')} />
<TextInput maxLength={1} keyboardType={'numeric'} ref={this.digit2Ref} style={styles.boxStyle} value={this.state.digit2} onChangeText={(text) => this.saveText(text, 'digit2')} />
<TextInput maxLength={1} keyboardType={'numeric'} ref={this.digit3Ref} style={styles.boxStyle} value={this.state.digit3} onChangeText={(text) => this.saveText(text, 'digit3')} />
</>
)
}
}
const styles = StyleSheet.create({
boxStyle: {
marginTop: 20,
height: 57,
width: 50,
borderRadius: 10,
borderWidth: 1,
borderColor: '#F1F5F9',
backgroundColor: '#F1F5F9',
fontSize: 20,
lineHeight: 40,
paddingHorizontal: 15,
textAlign: 'center'
}
})
export default BoxInput;
import React, { createRef } from 'react';
import styles from './style';
import {
View,
TextInput,
Alert
} from "react-native";
import { connect } from "react-redux";
import * as Animatable from 'react-native-animatable';
import BoxInput from "../../../../md-components/atoms/boxinput"
class MPINScreen extends React.Component {
constructor(props) {
super(props)
this.state = {
confirmMpinEnable: true,
...props
}
this.codes = [{
value: '+91',
}]
}
componentDidUpdate(prevProps) {
if (this.state.mpinValue.split("").length == 3 &&
prevProps.success_msg != this.props.success_msg && this.props.success_msg == 'verified') {
NavigationService.navigate(this.props.navigation, 'MPINVerifyOnboarding')
}
}
handleSubmit = () => {
if (this.state.mpinValue != this.state.confirmMpinValue) {
Alert.alert(
"Error",
"MPIN is not machted",
[
{ text: "OK" }
],
{ cancelable: false }
);
} else {
this.props.verifyMpin({
"mpin": this.state.mpinValue,
phoneNumber: this.props.mobileNumber
})
}
}
mpinConfirmation = () => {
if (this.state.mpinValue.split("").length != 6) {
Alert.alert(
"Error",
"Please insert 6 digit mpin",
[
{ text: "OK" }
],
{ cancelable: false }
);
}else{
this.setState({
confirmMpinEnable: false,
});
}
}
mpinText = (args) => {
this.setState({
mpinValue: args,
});
}
confirmMpinText = (args) => {
this.setState({
confirmMpinValue: args,
});
}
render() {
return (
<>
<HeaderComponent backgroundColor="#E5E5E5" showLeftIcon={true} showRightIcon={false} />
<View style={styles.container}>
<Text style={[styles.textInfo, styles.textTitle]}>We are almost there!</Text>
<View style={styles.imageWrapper}>
<Animatable.View animation="slideInDown" iterationCount={1} style={styles.centerIconWrap}>
<Image style={styles.centerIcon} source={mpin_card} />
</Animatable.View>
</View>
{this.state.confirmMpinEnable ?
<Text style={[styles.textInfo]}>Setup your MPIN</Text> : <Text style={[styles.textInfo]}>Confirm your MPIN</Text>
}
{this.state.confirmMpinEnable ?
<View style={styles.rowWrap}>
<BoxInput id="catFood1" onBoxInput={this.mpinText} />
</View>:
<View style={styles.rowWrap}>
<BoxInput id="catFood2" onBoxInput={this.confirmMpinText} />
</View>
}
<View style={styles.marginBottom}>
<Text style={[styles.mpinNote]}>M-PIN is a short 6-digit PIN that you have to set for</Text>
<Text style={[styles.mpinNote]}>our mandatory Two-Factor Authentication</Text>
</View>
<View style={styles.bottomBtnSyle}>
<View style={styles.multipleBtnStyle}>
<Button onPress={this.handleBack}>{"Back"}</Button>
</View>
{this.state.confirmMpinEnable ?
<View style={styles.multipleBtnStyle}>
<Button onPress={this.mpinConfirmation} >{"Confirm"}</Button>
</View> :
<View style={styles.multipleBtnStyle}>
<Button onPress={this.handleSubmit} >{"Save & Continue"}</Button>
</View>
}
</View>
</View>
</>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MPINScreen);
when I am click on confirm button then hide and display . But in second component data is prepopulating which i was inserted.
in this screen shot data is prepopulate but i want this empty, Because user has to insert again. but it is taking same value from previous state. how we can use multiple time same component in one page.
General idea:
Create a property in MPINScreen state that is changing (incrementing) every attempt (you can call it attempt) and pass it as prop to BoxInput.
In BoxInput create a reset function (that will clean the values of the text inputs and focus the first input). On componentDidUpdate check if attempt prop changed. If true - save the new value in BoxInput state and call "reset".

FlatList not updated if data updated

I have a FlatList with data fetched from an API. There's a button on the screen that fetches data which is changed and sets the state, but the flat list doesn't refresh. I tried setting the extraData as per docs, but it didn't help. Here are the full code and snack.
If you click the Toggle List button, the alert correctly shows the new data, but the list isn't updated.
import React, {useState} from 'react';
import { SafeAreaView, View, FlatList, StyleSheet, Text, Button } from 'react-native';
import Constants from 'expo-constants';
const DATA2 = [
{
id: 0,
title: 'D2-0'
},
{
id: 1,
title: 'D2-1'
},
{
id: 2,
title: 'D2-2'
},
];
const DATA1 = [
{
id: 0,
title: 'D1-0'
},
{
id: 1,
title: 'D1-1'
},
{
id: 2,
title: 'D1-2'
},
];
export default function App(props) {
const [data, setData]=useState(DATA1);
const [dataUsed, setDataUsed]=useState(1);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={data}
renderItem={({ item }) => <MyComponent data={item} /> }
keyExtractor={item => item.id}
extraData={data}
/>
<Button title="Toggle Data" onPress={() => {
let newData = dataUsed === 1 ? DATA2 : DATA1;
setDataUsed(dataUsed === 1 ? 2: 1);
alert(JSON.stringify(newData));
setData(newData);
}} />
</SafeAreaView>
);
}
class MyComponent extends React.Component {
constructor(props){
super(props);
this.state = {data: props.data};
}
render() {
return <Text>{this.state.data.title}</Text>;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: Constants.statusBarHeight,
padding: 50
}
});
<div data-snack-id="SkDYPf4wH" data-snack-platform="web" data-snack-preview="true" data-snack-theme="light" style="overflow:hidden;background:#fafafa;border:1px solid rgba(0,0,0,.08);border-radius:4px;height:505px;width:100%"></div>
<script async src="https://snack.expo.io/embed.js"></script>
I think you missed the reflection of the state.
Once you set the state, it could be reflected next time.
Do I think you need to use the Hook.
Please try to use it.
import React, {useState, useEffect} from 'react';
... ... ...
export default function App(props) {
const [data, setData]=useState(DATA1);
const [dataUsed, setDataUsed]=useState(1);
useEffect(()=>{
let newData = dataUsed === 1 ? DATA2 : DATA1;
setData(newData);
},[setData, dataUsed]);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={data}
renderItem={({ item }) => <MyComponent data={item} /> }
keyExtractor={item => item.id}
extraData={data}
/>
<Button title="Toggle Data" onPress={() => {
setDataUsed(dataUsed === 1 ? 2: 1);
alert(JSON.stringify(newData));
}} />
</SafeAreaView>
);
}
And for the component.
class MyComponent extends React.Component {
constructor(props){
super(props);
this.state = {data: props.data};
}
componentDidUpdate(prevProps){
if( prevProps.data !== this.props.data ){
this.setData();
}
}
setData = ()=>{
this.setState({
data: this.props.data,
});
}
Render () {
return <Text>{this.state.data.title}</Text>;
}
Change MyComponent code like this
class MyComponent extends React.Component {
render() {
return <Text>{this.props.data.title}</Text>;
}
}
Your constructor code is actually useless.

Multi select dropdown in react native

I am new to react native. Can anyone suggest how do i implement multiple select dropdown in react native. I have tried MultiSelect (https://github.com/toystars/react-native-multiple-select) from react-native-multiple-select but it is not working.
This is a source code of implemented multiselect source list
import React from 'react';
import {View, Text, StyleSheet, FlatList, TouchableHighlight, Dimensions} from 'react-native';
var thisObj;
var deviceHeight = Dimensions.get("window").height;
class MyListItem extends React.PureComponent {
render() {
return (
<View style={{flex: 1}}>
<TouchableHighlight onPress={this.props.onPress.bind(this)} underlayColor='#616161'>
<Text style={this.props.style}>{this.props.item.key}</Text>
</TouchableHighlight>
</View>
);
}
}
export default class MultiSelect extends React.Component {
constructor(props) {
super(props);
var selectedItemsObj = {};
if(this.props.selectedItems) {
var items = this.props.selectedItems.split(',');
items.forEach(function(item) {
selectedItemsObj[item] = true;
});
}
this.state = {
selectedItems: selectedItemsObj
};
}
onItemPressed(item) {
var oldSelectedItems = this.state.selectedItems;
var itemState = oldSelectedItems[item.key];
if(!itemState) {
oldSelectedItems[item.key] = true;
}
else {
var newState = itemState? false: true;
oldSelectedItems[item.key] = newState;
}
this.setState({
selectedItems: oldSelectedItems,
});
var arrayOfSelectedItems = [];
var joinedItems = Object.keys(oldSelectedItems);
joinedItems.forEach(function(key) {
if(oldSelectedItems[key])
arrayOfSelectedItems.push(key);
});
var selectedItem = null;
if(arrayOfSelectedItems.length > 0)
selectedItem = arrayOfSelectedItems.join();
this.props.onValueChange(selectedItem);
}
componentWillMount() {
thisObj = this;
}
getStyle(item) {
try {
console.log(thisObj.state.selectedItems[item.key]);
return thisObj.state.selectedItems[item.key]? styles.itemTextSelected : styles.itemText;
} catch(e) {
return styles.itemText;
}
}
_renderItem = ({item}) => {
return (<MyListItem style={this.getStyle(item)} onPress={this.onItemPressed.bind(this, item)} item={item} />);
}
render() {
return (
<View style={styles.rootView}>
<FlatList style={styles.list}
initialNumToRender={10}
extraData={this.state}
data={this.props.data}
renderItem={this._renderItem.bind(this)}
/>
</View>
);
}
}
const styles = StyleSheet.create({
rootView : {
height: deviceHeight / 2
},
itemText: {
padding: 8,
color: "#fff"
},
itemTextSelected: {
padding: 8,
color: "#fff",
backgroundColor: '#757575'
},
list: {
flex: 1,
}
});
This is how the component should be used
this.state = {
selectedItem : null,
data: [{key:"key1", label:"label1"}, {key:"key2", label:"label2"}]
}
...
<MultiSelect
data={this.state.data}
selectedItems={this.state.selectedItem}
onValueChange={ (itemValue) => thisObj.setState({selectedItem: itemValue})}/>
Selected values will be joined and set in this.state.selectedItem field
I have implemented React Native component.
Source code is attached.
It shows how to make list checkable.
It may be a base for your solution.
Please see.
import React from 'react';
import {View, Text, StyleSheet, FlatList, TouchableHighlight} from 'react-native';
var thisObj;
export default class MultiSelect extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedItems: {}
};
}
onItemPressed(item) {
var oldSelectedItems = this.state.selectedItems;
var itemState = oldSelectedItems[item.key];
if(!itemState) {
oldSelectedItems[item.key] = true;
}
else {
var newState = itemState? false: true;
oldSelectedItems[item.key] = newState;
}
this.setState({
selectedItems: oldSelectedItems,
});
}
componentDidMount() {
thisObj = this;
}
getStyle(item) {
try {
console.log(thisObj.state.selectedItems[item.key]);
return thisObj.state.selectedItems[item.key]? styles.itemTextSelected : styles.itemText;
} catch(e) {
return styles.itemText;
}
}
render() {
return (
<View style={styles.rootView}>
<FlatList style={styles.list}
extraData={this.state}
data={this.props.data}
renderItem={({item}) =>
<TouchableHighlight onPress={this.onItemPressed.bind(this, item)} underlayColor='#f00'>
<Text style={this.getStyle(item)}>{item.key}</Text>
</TouchableHighlight>
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
rootView : {
},
itemText: {
padding: 16,
color: "#fff"
},
itemTextSelected: {
padding: 16,
color: "#fff",
backgroundColor: '#f00'
},
list: {
}
});
How to use this
<Multiselect data = { [{"key" : "item1"}, {"key" : "item2"}{"key" : "item3"}]
}\>

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.

React Native - Conditional Styles on Parent Based on Child State

I was following the React Native tutorial and have tried to adapt it to show a list of songs rather than movies and add in a toggling ability using the Switch component.
I managed to get this to work but now I am trying to send the value of the switch back to the parent so that a conditional style can be applied.
When I attempted to do this, I get an error saying
undefined is not an object (evaluating 'this.state.played')
which seems sensible since the console statement in the togglePlayed never seems to be called.
import React, {
AppRegistry,
Component,
Image,
ListView,
StyleSheet,
Text,
View,
Switch
} from 'react-native';
var SONGS_DATA = {
"songs" : [
{
"title" : "I Heard React Was Good",
"artist" : "Martin",
"played" : false
},
{
"title" : "Stack Overflow",
"artist" : "Martin",
"played" : false
}
]
}
var BasicSwitchExample = React.createClass({
getInitialState() {
return {
played: false
};
},
handlePlayed(value) {
console.log('Switch has been toggled, new value is : ' + value)
this.setState({played: value})
this.props.callbackParent(value);
},
render() {
return (
<View>
<Switch
onValueChange={this.handlePlayed}
style={{marginBottom: 10}}
value={this.state.played} />
</View>
);
}
});
class AwesomeProject extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
loaded: false,
};
}
componentDidMount() {
this.fetchData();
}
getInitialState() {
return {
played: false
};
}
togglePlayed(value) {
// this is never reached
this.setState({played: value});
console.log('Song has been played? ' + this.state.played);
}
fetchData() {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(SONGS_DATA.songs),
loaded: true,
});
}
render() {
if (!this.state.loaded) {
return this.renderLoadingView();
}
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderSong}
style={styles.listView}
/>
);
}
renderLoadingView() {
return (
<View style={styles.container}>
<Text>
Loading songs...
</Text>
</View>
);
}
renderSong(song) {
return (
// not sure if this syntax is correct
<View style={this.state.played ? 'styles.container' : 'styles.played'}>
<View style={styles.half}>
<Text style={styles.title}>{song.title}</Text>
<Text style={styles.artist}>{song.artist}</Text>
</View>
<View style={styles.half}>
<BasicSwitchExample callbackParent={() => this.togglePlayed} />
</View>
</View>
);
}
}
var styles = StyleSheet.create({
/* styles here */
});
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
React Native Playground
Any pointers would be great as I am new to React and especially React Native.
You forgot to bind your function to your component, it should look like this
class BasicSwitchExample extends Component{
constructor(props){
super(props);
this.state = {
played: false
};
this.handlePlayed = this.handlePlayed.bind(this);
}
handlePlayed(value){
this.setState({played: value});
this.props.callbackParent(value);
}
render() {
return <View>
<Switch
onValueChange={this.handlePlayed}
style={{marginBottom: 10}}
value={this.state.played} />
</View>
}
}
class AwesomeProject extends Component {
constructor(props) {
super(props);
this.renderSong = this.renderSong.bind(this);
this.togglePlayed = this.togglePlayed.bind(this);
this.fetchData = this.fetchData.bind(this);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
loaded: false,
};
}
componentDidMount() {
this.fetchData();
}
togglePlayed(value) {
// this is never reached
this.setState({played: value});
console.log('Song has been played? ' + this.state.played);
}
fetchData() {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(SONGS_DATA.songs),
loaded: true,
});
}
render() {
if (!this.state.loaded) {
return this.renderLoadingView();
}
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderSong}
style={styles.listView}
/>
);
}
renderLoadingView() {
return (
<View style={styles.container}>
<Text>
Loading songs...
</Text>
</View>
);
}
renderSong(song) {
return (
// not sure if this syntax is correct
<View style={this.state.played ? 'styles.container' : 'styles.played'}>
<View style={styles.half}>
<Text style={styles.title}>{song.title}</Text>
<Text style={styles.artist}>{song.artist}</Text>
</View>
<View style={styles.half}>
<BasicSwitchExample callbackParent={this.togglePlayed} />
</View>
</View>
);
}
}

Resources