Context
Im want to get a countdown value which I then want to pass as prop to a separate component animating a progress bar. the data.json contains two type of elements:
text
question
the text will present the user an explanation. when the user pushes the button another text can be shown or a question. when it is a question the user will have certain amount of time for the answer. the progress bar will indicate the remaining time and shall only be shown when the current element is a question.
Problem
My problem is that the screen update for the current value is super slow and I can't see the reason. here is a simplified version of my code. the progress bar component will replace the <Text>{this.state.value}</Text> within the renderButtons function
Versions
I am running react native 0.60.
Update 1
I found out that when I trigger the countdown function by an onPress event, it works. but if I call it directly it doesn't. Why is that? How could I achieve it without the onPress
Update 2
the problem is that due to the animation the state value gets updated an by that a serenader gets triggered, which causes the function to be called each time, which is causing the lagging. this workaround seems to help but it feels ugly.
what would be a better approach to handle the re-rendering issue?
countdown = type => {
if (!this.state.countdownRunning) {
this.setState({ countdownRunning: true });
if (type === 'question') {
this.state.percent.addListener(({ value }) => this.setState({ value }));
Animated.timing(
// Animate value over time
this.state.percent, // The value to drive
{
toValue: 0, // Animate to final value of 1
duration: 25000,
easing: Easing.linear
}
).start(() => {
console.log('Animation DONE');
this.setState({ value: 100, percent: new Animated.Value(100) });
this.onPressNext();
this.setState({ countdownRunning: false });
}); // Start the animation
}
}
};
Lagging
import React, { Component } from 'react';
import { View, Text, StyleSheet, Animated, Easing } from 'react-native';
import { Button } from 'react-native-paper';
import Card from '../components/Card';
import data from '../data/data.json';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF'
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5
},
surface: {
padding: 20,
margin: 20,
borderRadius: 10,
alignItems: 'center',
justifyContent: 'center',
elevation: 12
}
});
class Home extends Component {
constructor(props) {
super(props);
this.countdown = this.countdown.bind(this);
this.state = {
i: 0,
questions: data,
value: 100,
percent: new Animated.Value(100)
};
}
onPressNext = () => {
const { i } = this.state;
if (i < 17) {
this.setState({ i: i + 1 });
} else {
this.setState({ i: 0 });
}
};
onPressBack = () => {
const { i } = this.state;
if (i > 0) {
this.setState({ i: i - 1 });
} else {
this.setState({ i: 17 });
}
};
countdown = type => {
if (type === 'question') {
this.state.percent.addListener(({ value }) => this.setState({ value }));
Animated.timing(
// Animate value over time
this.state.percent, // The value to drive
{
toValue: 0, // Animate to final value of 1
duration: 25000,
easing: Easing.linear
}
).start(); // Start the animation
}
};
renderButtons = type => {
if (type === 'question') {
this.countdown(type);
return (
<View>
<Text>{this.state.value}</Text>
</View>
);
}
return (
<View style={{ flexDirection: 'row' }}>
<Button mode="text" onPress={() => this.onPressBack()}>
Zurück
</Button>
<Button mode="contained" onPress={() => this.onPressNext(type)}>
Weiter
</Button>
</View>
);
};
render() {
const { i, questions } = this.state;
const { type, header, content } = questions.data[i.toString()];
return (
<View style={styles.container}>
<View style={{ flex: 2, justifyContent: 'flex-end' }}>
<Card>
<Text>{header}</Text>
<Text>{content}</Text>
</Card>
</View>
<View style={{ flex: 2 }}>{this.renderButtons(type)}</View>
</View>
);
}
}
export default Home;
No Lagging
import React, { Component } from 'react';
import { View, Text, StyleSheet, Animated, Easing } from 'react-native';
import { Button } from 'react-native-paper';
import Card from '../components/Card';
import data from '../data/data.json';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF'
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5
},
surface: {
padding: 20,
margin: 20,
borderRadius: 10,
alignItems: 'center',
justifyContent: 'center',
elevation: 12
}
});
class Home extends Component {
constructor(props) {
super(props);
this.countdown = this.countdown.bind(this);
this.state = {
i: 0,
questions: data,
value: 100,
percent: new Animated.Value(100)
};
}
onPressNext = () => {
const { i } = this.state;
if (i < 17) {
this.setState({ i: i + 1 });
} else {
this.setState({ i: 0 });
}
};
onPressBack = () => {
const { i } = this.state;
if (i > 0) {
this.setState({ i: i - 1 });
} else {
this.setState({ i: 17 });
}
};
countdown = type => {
if (type === 'question') {
this.state.percent.addListener(({ value }) => this.setState({ value }));
Animated.timing(
// Animate value over time
this.state.percent, // The value to drive
{
toValue: 0, // Animate to final value of 1
duration: 25000,
easing: Easing.linear
}
).start(); // Start the animation
}
};
renderButtons = type => {
if (type === 'question') {
return (
<View>
<Text>{this.state.value}</Text>
<Button mode="contained" onPress={() => this.countdown(type)}>
Weiter
</Button>
</View>
);
}
return (
<View style={{ flexDirection: 'row' }}>
<Button mode="text" onPress={() => this.onPressBack()}>
Zurück
</Button>
<Button mode="contained" onPress={() => this.onPressNext(type)}>
Weiter
</Button>
</View>
);
};
render() {
const { i, questions } = this.state;
const { type, header, content } = questions.data[i.toString()];
return (
<View style={styles.container}>
<View style={{ flex: 2, justifyContent: 'flex-end' }}>
<Card>
<Text>{header}</Text>
<Text>{content}</Text>
</Card>
</View>
<View style={{ flex: 2 }}>{this.renderButtons(type)}</View>
</View>
);
}
}
export default Home;
Getting a strange error when trying to apply conditional styling to a custom component. Whenever the style change should appear the text completely disappears. If I start typing again, the new styling appears but once the style would change again, the text disappears again. If I apply the styling as static, the custom styling works completely fine. I'm not sure what the issue could be. Thanks in advance for the help.
<UserInput
style = {!this.state.isValidEmail ? styles.errorInline : styles.default}
placeholder="Email"
autoCapitalize={'none'}
returnKeyType={'next'}
autoCorrect={false}
onSubmitEditing={() => this.focusNextField('password')}
updateState={(email) => {
let formattedEmail = email.trim();
this.state.initialValidationChecked? this.validate(formattedEmail) : this.setState({formattedEmail})}
}
blurOnSubmit={true}
onBlur2={(event) => this.validate(event.nativeEvent.text.trim())}
/>
errorInline: {
color: 'red',
},
default : {
color: '#777777'
}
export default class UserInput extends Component {
componentDidMount() {
if (this.props.onRef != null) {
this.props.onRef(this)
}
}
onSubmitEditing() {
if(this.props.onSubmitEditing){
this.props.onSubmitEditing();
}
}
focus() {
this.textInput.focus();
}
render() {
return (
<View style={styles.inputWrapper}>
<TextInput
style={[styles.input, this.props.style]}
placeholder={this.props.placeholder}
secureTextEntry={this.props.secureTextEntry}
autoCorrect={this.props.autoCorrect}
autoCapitalize={this.props.autoCapitalize}
returnKeyType={this.props.returnKeyType}
onChangeText={(value) => this.props.updateState(value)}
onEndEditing={(value) => { if(this.props.onBlur2) return this.props.onBlur2(value)}}
ref={input => this.textInput = input}
blurOnSubmit={this.props.blurOnSubmit}
onSubmitEditing={this.onSubmitEditing.bind(this)}
underlineColorAndroid='transparent'
/>
</View>
);
}
}
UserInput.propTypes = {
placeholder: PropTypes.string.isRequired,
secureTextEntry: PropTypes.bool,
autoCorrect: PropTypes.bool,
autoCapitalize: PropTypes.string,
returnKeyType: PropTypes.string,
};
const DEVICE_WIDTH = Dimensions.get('window').width;
const styles = StyleSheet.create({
input: {
width: DEVICE_WIDTH - 70,
height: 40,
marginHorizontal: 20,
marginBottom: 30,
color: '#777777',
borderBottomWidth: 1,
borderBottomColor: '#0099cc'
},
inputWrapper: {
justifyContent: 'center',
alignItems: 'center',
flex: 1,
},
});
Styles are given as object (key-value pair).
But looking at your codes in the following line
style = {!this.state.isValidEmail ? styles.errorInline : 'none'}
When this.state.isValidEmail returns true, you're just giving 'none' to the style, which is a syntax error, you should return something like this
style = {!this.state.isValidEmail ? styles.errorInline : {display: 'none'}}
I have created a game on React and I am trying to adapt my code to React Native. One of the things that is troubling me is how to translate these three lines, since in RN there are no DOM solutions to rely on:
handleClick(e) {
this.props.change(e.currentTarget.id);
}
What is happening here is that a stateless child is harvesting a clicked elements id (the currentTarget's) and is using it to call with it a method defined inside the parent. This kind of formulation e.currentTarget.id however does not work in RN.
Is there an eloquent way to re-write this one liner in RN?
Note: there are two questions vaguely resembling this one, here and here, however the answers look like more like patches than a structural elegant solution. If you are aware of something pls post an answer.
Edit: It seems that one cannot go around ReactNativeComponentTree.
I have that much so far but this does not work yet:
handlePress(event) {
let number = ReactNativeComponentTree.getInstanceFromNode(event.currentTarget)._currentElement.id;
this.props.change(number);
}
Second Edit: Ok maybe I should add a simplistic example of what I am trying to achieve. When I click on any of the flatlist's elements its id should be displayed on the Child's bottom . Clicking on reset will restore default state.
Code of simplistic example below:
import React, { Component } from 'react';
import { AppRegistry, FlatList, StyleSheet, Text, View, Button } from 'react-native';
import ReactNativeComponentTree from 'react-native';
export default class Parent extends Component {
constructor(props) {
super(props);
this.state = {
quotes: ["a","bnaskdkahhahskkdk","c","d","e","a","b","c","d"],
size: [true, true, true, true, true, true, true, true, true],
color: [false, false, false, false, false, false, false, false, false],
progress: "me"
};
this.change = this.change.bind(this);
this.reset = this.reset.bind(this);
}
change(number) {
this.setState({color: [true, true, true, true, true, true, true, true, true], progress: number});
}
reset() {
this.setState({color: [false, false, false, false, false, false, false, false, false],
progress: "me"
});
}
render() {
return (
<View style={styles.container}>
<Child change={this.change} reset={this.reset} quotes={this.state.quotes}
size={this.state.size} color={this.state.color}
progress={this.state.progress} />
</View>
);
}
}
class Child extends Component {
constructor(props) {
super(props);
this.handlePress = this.handlePress.bind(this);
this.handleReset = this.handleReset.bind(this);
}
/*handlePress(e) {
let number = e.currentTarget.id;
this.props.change(number);
}*/
handlePress(event) {
let number = ReactNativeComponentTree.getInstanceFromNode(event.currentTarget)._currentElement.id;
this.props.change(number);
}
handleReset() {
this.props.reset();
}
render() {
let ar = [];
for (let i=0; i<this.props.quotes.length; i++) {
let b = {key: `${i}`, id: i,
classSize: this.props.size[i] ? (i%2===0 ? styles.size : styles.oddsize) : "",
classColor: this.props.color[i] ? (i%2===0 ? styles.color : styles.oddcolor) : ""}
ar.push(b);
}
return (
<View style={styles.container}>
<Button onPress={this.handleReset} title="Reset" />
<FlatList
data={
ar
}
renderItem={({item}) => <Text onPress={this.handlePress}
style={[item.classSize, item.classColor]}> {item.id+1}
{this.props.quotes[item.id]} </Text> }
/>
<Text style={styles.size}>{this.props.progress}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "column",
//justifyContent: "center",
alignItems: "center",
paddingTop: 22,
//backgroundColor: "purple"
},
size: {
flex: 1,
padding: 10,
fontSize: 18,
backgroundColor: "grey",
margin: 1,
height: 44,
color: 'gold',
borderColor: "white",
borderWidth: "1",
textAlign: "center"
},
oddsize: {
flex: 1,
padding: 10,
fontSize: 18,
backgroundColor: "white",
margin: 1,
height: 44,
color: 'gold',
borderColor: "white",
borderWidth: "1",
textAlign: "center"
},
color: {
flex: 1,
padding: 10,
backgroundColor: 'grey',
//borderRadius: "25%",
margin: 1,
fontSize: 18,
height: 44,
color: 'pink',
borderColor: "red",
borderWidth: "1"
},
oddcolor: {
flex: 1,
padding: 10,
backgroundColor: 'white',
//borderRadius: "25%",
margin: 1,
fontSize: 18,
height: 44,
color: 'pink',
borderColor: "red",
borderWidth: "1"
}
})
// skip this line if using Create React Native App
AppRegistry.registerComponent('AwesomeProject', () => Parent);
A better way (avoiding event callbacks creation at every render)
to get the current pressed element properties (id in this example)
is by wrapping it in a parent component, passing data
and binding all events only once (in constructor)
First declare your component wrapper:
It needs to be a class and not a stateless functional component otherwise you can't avoid the callback creation at every render
to see the difference, here's a more advanced example in action:
https://snack.expo.io/ByTEKgEsZ (example source code)
class TouchableText extends React.PureComponent {
constructor(props) {
super(props);
this.textPressed = this.textPressed.bind(this);
}
textPressed(){
this.props.onPressItem(this.props.id);
}
render() {
return (
<Text style={styles.item} onPress={this.textPressed}>
{this.props.children}
</Text>
);
}
}
If you use a const JSX object (stateless functional component), it works but it's not optimal, the event callback will be created every time the component is rendered as the arrow function is actually a shortcut to the render function
const TouchableText = props => {
const textPressed = () => {
props.onPressItem(props.id);
};
return <Text onPress={textPressed} />;
};
Then use this wrapper instead of your component as following:
class Test extends React.Component {
constructor(props) {
super(props);
//event binding in constructor for performance (happens only once)
//see facebook advice:
//https://facebook.github.io/react/docs/handling-events.html
this.handlePress = this.handlePress.bind(this);
}
handlePress(id) {
//Do what you want with the id
}
render() {
return (
<View>
<FlatList
data={ar}
renderItem={({ item }) => (
<TouchableText
id={item.id}
onPressItem={this.handlePress}
>
{`Component with id ${item.id}`}
</TouchableText>
)}
/>
</View>
);
}
}
As written in React Native Handling Events Doc, there's another possible syntax to avoid binding in the constructor (experimental though: i.e. which may or may not be supported in the future!):
If calling bind annoys you, there are two ways you can get around this. If you are using the experimental property initializer syntax, you can use property initializers to correctly bind callbacks
this means we could only write
handlePress = (id) => {
//`this` is already bound!
}
instead of
constructor(props) {
super(props);
//manually bind `this` in the constructor
this.handlePress = this.handlePress.bind(this);
}
handlePress(id) {
}
Some references:
Event handlers and Functional Stateless Components
React Binding Patterns: 5 Approaches for Handling this
After 8 hours of searching I ve found the solution on my own, thanks to the fantastic calculator tutorial of Kyle Banks.
https://kylewbanks.com/blog/react-native-tutorial-part-3-developing-a-calculator
Well basically the solution is in binding item.id along this in the assignment of the onPress event listener, like so:
renderItem={({item}) => <Text
onPress={this.handlePress.bind(this, item.id)}
style={[item.classSize, item.classColor]}>
{item.id+1}
{this.props.quotes[item.id]}
</Text> }
After doing so, the only thing you have to do is to define handlePress like so:
handlePress(e) {
this.props.change(e);
}
Where e is the item.id, on the Child and change() on the Parent:
change(number) {
this.setState({color: [true, true, true, true, true, true, true, true, true], progress: number});
}
The complete code works as intended.
import React, { Component } from 'react';
import { AppRegistry, FlatList, StyleSheet, Text, View, Button } from 'react-native';
import ReactNativeComponentTree from 'react-native';
export default class Parent extends Component {
constructor(props) {
super(props);
this.state = {
quotes: ["a","bnaskdkahhahskkdk","c","d","e","a","b","c","d"],
size: [true, true, true, true, true, true, true, true, true],
color: [false, false, false, false, false, false, false, false, false],
progress: "me"
};
this.change = this.change.bind(this);
this.reset = this.reset.bind(this);
}
change(number) {
this.setState({color: [true, true, true, true, true, true, true, true, true], progress: number});
}
reset() {
this.setState({color: [false, false, false, false, false, false, false, false, false],
progress: "me"
});
}
render() {
return (
<View style={styles.container}>
<Child change={this.change} reset={this.reset} quotes={this.state.quotes}
size={this.state.size} color={this.state.color}
progress={this.state.progress} />
</View>
);
}
}
class Child extends Component {
constructor(props) {
super(props);
this.handlePress = this.handlePress.bind(this);
this.handleReset = this.handleReset.bind(this);
}
handlePress(e) {
this.props.change(e);
}
/* handlePress(event) {
let number = ReactNativeComponentTree.getInstanceFromNode(event.currentTarget)._currentElement.id;
this.props.change(number);
} */
handleReset() {
this.props.reset();
}
render() {
let ar = [];
for (let i=0; i<this.props.quotes.length; i++) {
let b = {key: `${i}`, id: i,
classSize: this.props.size[i] ? (i%2===0 ? styles.size : styles.oddsize) : "",
classColor: this.props.color[i] ? (i%2===0 ? styles.color : styles.oddcolor) : ""}
ar.push(b);
}
return (
<View style={styles.container}>
<Button onPress={this.handleReset} title="Reset" />
<FlatList
data={
ar
}
renderItem={({item}) => <Text onPress={this.handlePress.bind(this, item.id)}
style={[item.classSize, item.classColor]}> {item.id+1}
{this.props.quotes[item.id]} </Text> }
/>
<Text style={styles.size}>{this.props.progress}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "column",
//justifyContent: "center",
alignItems: "center",
paddingTop: 22,
//backgroundColor: "purple"
},
size: {
flex: 1,
padding: 10,
fontSize: 18,
backgroundColor: "grey",
margin: 1,
height: 44,
color: 'gold',
borderColor: "white",
borderWidth: "1",
textAlign: "center"
},
oddsize: {
flex: 1,
padding: 10,
fontSize: 18,
backgroundColor: "white",
margin: 1,
height: 44,
color: 'gold',
borderColor: "white",
borderWidth: "1",
textAlign: "center"
},
color: {
flex: 1,
padding: 10,
backgroundColor: 'grey',
//borderRadius: "25%",
margin: 1,
fontSize: 18,
height: 44,
color: 'pink',
borderColor: "red",
borderWidth: "1"
},
oddcolor: {
flex: 1,
padding: 10,
backgroundColor: 'white',
//borderRadius: "25%",
margin: 1,
fontSize: 18,
height: 44,
color: 'pink',
borderColor: "red",
borderWidth: "1"
}
})
// skip this line if using Create React Native App
AppRegistry.registerComponent('AwesomeProject', () => Parent);
"objects are not valid as a react child (found: object with keys {date, events}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of View."
So I have kind of a cascade of method calls. I'm retrieving dates with events inside of those. It feels like I'm doing this correctly, but am getting the above error. I've tried setting createFragment on places, but still getting the error. Here's the code:
import React, { Component } from 'react';
import {
AppRegistry,
Text,
View,
ScrollView,
RefreshControl,
StyleSheet,
Dimensions,
TextInput,
Linking,
TouchableNativeFeedback
} from 'react-native';
var _ = require('lodash');
var {width, height} = Dimensions.get('window');
var renderif = require('render-if');
var createFragment = require('react-addons-create-fragment');
var IMAGES_PER_ROW = 1
class FunInATL extends Component {
constructor(props) {
super(props);
this.state = {
currentScreenWidth: width,
currentScreenHeight: height,
dates: [],
boxIndex: 0,
showBox: false,
refreshing: false
};
}
handleRotation(event) {
if (!this.state) {
return;
}
var layout = event.nativeEvent.layout
this.state({currentScreenWidth: layout.width, currentScreenHeight: layout.height })
}
calculatedSize() {
var size = this.state.currentScreenWidth / IMAGES_PER_ROW
return {width: size}
}
renderRow(events) {
return events.map((events, i) => {
return (
<Image key={i} style={[this.getImageStyles(), styles.image, this.calculatedSize() ]} source={{uri: event.image}} />
)
})
}
openUrl(url) {
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
} else {
console.log('nope :: ' + url);
}
}).catch(err => console.error('An error occurred', err));
}
getImageStyles(featured, category) {
let options = {
borderColor: 'gold',
borderWidth: featured ? 1 : 0
}
if (!category) {
options.height = featured ? 250 : 125
}
return options;
}
_clickImage(event, index) {
if (event.title) {
let new_val = !this.state.showBox
this.setState({
dates: this.state.dates,
showBox: new_val,
boxIndex: new_val ? index : 0
});
}
}
componentDidMount() {
this.state = {
dates: [],
boxIndex: 0,
showBox: false,
refreshing: false
};
this.getApiData();
Linking.addEventListener('url', this.handleUrl);
}
componentWillUnmount() {
Linking.removeEventListener('url', this.handleUrl);
}
getApiData() {
var _this = this;
return fetch('https://www.funinatl.com/mobile2.php?v1')
.then(function(response) {
return response.json()
})
.then((responseJson) => {
var dates = createFragment(responseJson.events)
return;
let _this = this;
date.events.map((event, i) => (
date.events[i] = event
))
var datesData = [];
dates.map((date, i) => (
datesData.push({
date: i,
events: createFragment(date.events)
})
))
_this.setState({
dates: createFragment(datesData),
boxIndex: 0,
showBox: false
})
console.error(this.state);
})
.catch((error) => {
console.error(error);
})
.done();
}
renderDates() {
return this.state.dates.map((date) =>
(
<View>
<Text style={styles.dateHeader}>{ date.date }</Text>
<View>
{this.renderEvents(date.events)}
</View>
</View>
))
}
renderImage(event, index) {
if (this.state.showBox && this.state.boxIndex == index) {
return (
<View>
<TouchableNativeFeedback onPress={()=>this._clickImage(event, index)}>
<Image source={{ uri: event.image }} style={[styles.image, this.calculatedSize(), this.getImageStyles(event.featured), { height: 100 }]} />
</TouchableNativeFeedback>
<View style={{ flexDirection:'row', padding: 15 }}>
<Text style={styles.price}>{event.price}</Text>
<Text style={styles.time}>{event.time}</Text>
<TouchableNativeFeedback onPress={()=>this.openUrl(event.website)}>
<Text style={styles.btn}>Website</Text>
</TouchableNativeFeedback>
</View>
{renderif(event.venue)(
<TouchableNativeFeedback onPress={()=>this.openUrl(event.venue)}>
<Text style={styles.btn}>Venue</Text>
</TouchableNativeFeedback>
)}
</View>
)
} else {
return (
<View>
<TouchableNativeFeedback onPress={()=>this._clickImage(event, index)}>
<Image source={{ uri: event.image }} style={[styles.image, this.calculatedSize(), this.getImageStyles(event.featured)]} />
</TouchableNativeFeedback>
</View>
)
}
}
renderEvents(events) {
return events.map((event, i) =>
(
<View>
<Text style={[styles.eventCategory, this.getImageStyles(event.featured, true)]}>{event.category}</Text>
<View>
{this.renderImage(event, i)}
</View>
<Text style={[styles.eventTitle, this.getImageStyles(event.featured, true)]}>{event.title}</Text>
</View>
));
}
_onRefresh() {
this.setState({refreshing: true});
fetchData().then(() => {
this.setState({refreshing: false});
});
}
render() {
return (
<ScrollView onLayout={this.handleRotation} contentContainerStyle={styles.scrollView} refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh.bind(this)}
tintColor="#ff0000"
title="Loading..."
titleColor="#00ff00"
colors={['#ff0000', '#00ff00', '#0000ff']}
progressBackgroundColor="#ffff00"
/>
}>
<Text style={styles.header}>FunInATL</Text>
{this.renderDates()}
</ScrollView>
)
}
}
var styles = StyleSheet.create({
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
textAlign: 'center',
padding: 10
},
header: {
fontSize: 30,
fontWeight: 'bold',
padding: 20,
textAlign: 'center',
backgroundColor: '#000',
color: '#fff'
},
dateHeader: {
fontSize: 20,
fontWeight: 'bold',
padding: 20,
textAlign: 'left',
color: '#fff',
backgroundColor: '#283593'
},
eventCategory: {
backgroundColor: '#03a9f4',
textAlign: 'center',
color: '#ffffff',
padding: 3
},
eventTitle: {
borderTopWidth: 0,
textAlign: 'center',
fontWeight: 'bold',
padding: 3,
fontSize: 18,
},
image: {
},
btn: {
backgroundColor: 'green',
padding: 10,
color: '#fff',
textAlign: 'center',
flex: 1
},
price: {
marginLeft: 10,
fontSize: 16,
flex: 1
},
time: {
marginRight: 10,
fontSize: 16,
flex: 1,
width: 35
}
});
AppRegistry.registerComponent('FunInATL', () => FunInATL);
Thanks!
EDIT: Updated code per the map suggestion, still not working. complaining about {events} only now.
EDIT 2: Updated with FULL code.
The component's render helpers, such as renderDates(), are returning _.each(...). _.each() returns its first argument so this is why you are receiving the error.
To illustrate:
const myObject = { a: 1 };
_.each(myObject) === myObject // true
I recommend you use Array.prototype.map() instead:
return this.state.dates.map((date) => (
<View>...</View>
));
If you use arrow functions like I did in the example above, there's no need to save a reference to this. this in the body of the function passed to map() will be bound to the instance of the component. You can then call other helper methods such as getImageStyles() like this.getImageStyles(...).
This is not related to your original question but the getApiData() method will not work. You can replace the function in the chain that handles responseJson with something like:
(responseJson) => {
this.setState({
dates: Object.entries(responseJson.events).map(([date, { events }]) => ({
date,
events,
})),
boxIndex: 0,
showBox: false,
});
}
You also need to to remove the this.state = {...} in componentDidMount(). Notice the warning in the docs that indicates you should "NEVER mutate this.state directly".