Related
I was trying to build a social media application. In the profile screen I have Flatlist that renders the information. It is alright when I'm in dubugging mode or in normal exporting mode (without different cpu architecures) but when I try to export and assemble the output apk with different architecture ap stops working when we are touching the profile bottom tap icon.
Do you have any idea what is happening when we are trying to export and assemble the release apk?
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable react-native/no-inline-styles */
import React, {useState, useEffect, useContext} from 'react';
import {
StyleSheet,
View,
TouchableOpacity,
KeyboardAvoidingView,
FlatList,
RefreshControl,
Text,
} from 'react-native';
import {Snackbar} from 'react-native-paper';
import jwtDecode from 'jwt-decode';
import Feather from 'react-native-vector-icons/dist/Feather';
import {AppScreen} from '../../Layouts';
import {
AppProfileHeader,
AppProfileBio,
AppProfileBioContent,
AppProfilePosts,
AppProfileTopNavHeader,
AppBioCallToActions,
AppProfileStepsSlider,
} from '../../Containers';
import {
AppInputContainer,
AppText,
ContactUserModal,
AppButton,
ProfileCommentsListModal,
AppUserPostComment,
AppCommentInput,
AppPostUploaderBox,
} from '../../Components';
import {Colors, Fonts, ScreenSize} from '../../Constants';
import {ScrollView} from 'react-native-gesture-handler';
import {AppContext, AuthStorage} from '../../Services';
import {ProfileApis, SettingsApis} from '../../Services/Apis';
export default function ProfileScreen({navigation}) {
const {
user,
setUser,
setUserPosts,
userPosts,
setUserProfileInfo,
userProfileInfo,
} = useContext(AppContext);
const restoreToken = async () => {
const token = await AuthStorage.getToken();
if (!token) {
return;
}
const decodedToken = jwtDecode(token);
setUser(decodedToken);
};
const [refreshing, setRefreshing] = useState(false);
const [userCategoryType, setUserCategoryType] = useState();
const [commentAccountText, setCommentAccountText] = useState('');
const [accountCommentList, setAccountCommentList] = useState('');
const [editUserConnectionInputs, setEditUserConnectionInputs] = useState(
false,
);
const [contactUserInfo, setContactUserInfo] = useState({});
const contactUserInfoTextChangeHandler = (key, text) => {
setContactUserInfo((prev) => {
return {
...prev,
[key]: text,
};
});
};
const [snackbarVisible, setSnackbarVisible] = useState(false);
const onDismissSnackBar = () => setSnackbarVisible(false);
const [contactModalVisible, setContactModalVisible] = useState(false);
const [profileCommentsVisible, setProfileCommentsVisible] = useState(false);
const modalVisibilityHandler = () => {
setContactModalVisible(!contactModalVisible);
};
const updateUserContactInfoHandler = async () => {
try {
const result = await SettingsApis.updateAccountDatum({
datum: {...contactUserInfo},
});
if (result.data.status) {
setEditUserConnectionInputs(false);
modalVisibilityHandler();
setSnackbarVisible(true);
}
} catch (error) {
console.log("can't update user contactInfo", error.message);
}
};
const profileCommentVisibilityHandler = () => {
setProfileCommentsVisible(!profileCommentsVisible);
};
const topHeaderNavigationHandler = (route, param) => {
navigation.navigate(route, param);
};
const imagePickerHandler = () => {
navigation.jumpTo('CreatePost');
};
const fetchProfileInfo = async () => {
if (await AuthStorage.getToken()) {
try {
const result = await ProfileApis.profileInfo({
account: user.xdefaultaccount,
});
if (result.data.status) {
setUserProfileInfo(result.data.datum);
setContactUserInfo(result.data.datum);
setRefreshing(false);
}
} catch (error) {
console.log(error.message);
}
}
};
const fetchPosts = async () => {
try {
const result = await ProfileApis.getPosts({
account: userProfileInfo.id,
});
if (result.data.status) {
setUserPosts(result.data.datum);
} else {
console.log('fetch error');
}
} catch (error) {}
};
const updateStar = async (score) => {
try {
const result = await ProfileApis.updateStar({
node: userProfileInfo.id,
vote: score,
});
if (result.status) {
fetchProfileInfo();
}
} catch (error) {
console.log(error.message);
}
};
const getAccountComments = async () => {
try {
const result = await ProfileApis.getAccountComments({
account: userProfileInfo.id,
});
if (result) {
setAccountCommentList(result.data.datum);
}
} catch (error) {
console.log(error.message);
}
};
const registerCommentAccount = async () => {
try {
const result = await ProfileApis.registerCommentAccount({
node: userProfileInfo.id,
content: commentAccountText,
});
if (result) {
getAccountComments();
setCommentAccountText('');
}
} catch (error) {
console.log(error.message);
}
};
const registerCommentLike = async (id) => {
try {
const result = await ProfileApis.registerCommentLike({
node: id,
});
if (result.data.status) {
getAccountComments();
}
} catch (error) {
console.log("can't register comment like", error.message);
}
};
const followUser = async () => {
try {
const result = await ProfileApis.follow({
node: userProfileInfo.id,
});
if (result) {
console.log(result);
}
} catch (error) {
console.log(error.message);
}
};
const updateStepHandler = async (step) => {
let fetchedSteps = userProfileInfo.step;
try {
if (fetchedSteps) {
fetchedSteps.splice(fetchedSteps.indexOf(step), 1);
}
const result = await SettingsApis.updateAccountStep({
step: fetchedSteps,
});
if (result.data.status) {
fetchProfileInfo();
}
} catch (error) {
console.log("can't update account step", error.message);
}
};
useEffect(() => {
restoreToken();
}, []);
useEffect(() => {
fetchProfileInfo();
}, [user]);
useEffect(() => {
fetchPosts();
getAccountComments();
setUserCategoryType(userProfileInfo.category);
}, [userProfileInfo]);
return (
<AppScreen style={styles.container}>
<AppProfilePosts
{...{
refreshControl: (
<RefreshControl refreshing={refreshing} onRefresh={restoreToken} />
),
posts: userPosts,
navigation,
ListEmptyComponent: () => {
return (
<>
<AppPostUploaderBox {...{imagePickerHandler}} />
</>
);
},
ListHeader: () => {
return (
<>
<AppProfileTopNavHeader
{...{
navigation,
topHeaderNavigationHandler,
username: userProfileInfo.account,
userProfileInfo,
user,
}}
/>
<AppProfileHeader
{...{fetchedProfileData: userProfileInfo, navigation}}
/>
<AppProfileBio
{...{
fetchedProfileData: userProfileInfo,
updateStar,
navigation,
}}>
<AppProfileBioContent
{...{fetchedProfileData: userProfileInfo}}
/>
<AppBioCallToActions
{...{
userProfileInfo,
user,
followUser,
modalVisibilityHandler,
profileCommentVisibilityHandler,
}}
/>
</AppProfileBio>
{userCategoryType === 'personal' ? (
<AppProfileStepsSlider
{...{
avatarUri: userProfileInfo.avatar,
steps: userProfileInfo.step,
updateStepHandler,
fetchProfileInfo,
}}
/>
) : null}
</>
);
},
}}
/>
<ContactUserModal
style={[styles.wrapperStyle]}
{...{
modalVisibilityHandler,
isVisible: contactModalVisible,
}}>
<ScrollView>
<View style={styles.contentWrapper}>
<TouchableOpacity
style={styles.closeButtonContainer}
onPress={modalVisibilityHandler}>
<Feather name="x" style={styles.closeIcon} />
</TouchableOpacity>
<View style={styles.titleContainer}>
<AppText
style={{
fontFamily: Fonts.iransansMedium,
fontSize: 16,
marginRight: 10,
textAlign: 'right',
}}>
{contactUserInfo.firstname} {contactUserInfo.lastname}
</AppText>
<AppText style={{padding: 8}}>
{contactUserInfo.biography}
</AppText>
</View>
<AppInputContainer
onChangeText={(text) =>
contactUserInfoTextChangeHandler('mobile', text)
}
editable={editUserConnectionInputs}
value={contactUserInfo.mobile}
placeholder="شماره موبایل"
inputStyle={{
textAlign: 'left',
fontSize: 16,
top: 2,
color: '#838383',
}}
IconComponent={
<AppText
style={{
fontFamily: Fonts.iransans,
fontSize: 16,
color: '#838383',
}}>
+98
</AppText>
}
/>
<AppInputContainer
onChangeText={(text) =>
contactUserInfoTextChangeHandler('phone', text)
}
placeholder="تلفن ثابت"
value={contactUserInfo.phone}
editable={editUserConnectionInputs}
inputStyle={{
textAlign: 'left',
fontSize: 16,
top: 2,
color: '#838383',
paddingLeft: 20,
}}
/>
<AppInputContainer
onChangeText={(text) =>
contactUserInfoTextChangeHandler('webpage', text)
}
value={contactUserInfo.webpage}
editable={editUserConnectionInputs}
placeholder="وبسایت"
inputStyle={{
textAlign: 'left',
paddingLeft: 15,
fontSize: 16,
top: 2,
color: '#838383',
}}
/>
<AppInputContainer
onChangeText={(text) =>
contactUserInfoTextChangeHandler('province', text)
}
placeholder="استان"
value={contactUserInfo.province}
editable={editUserConnectionInputs}
inputStyle={{
fontSize: 14,
top: 2,
color: '#838383',
}}
/>
<AppInputContainer
onChangeText={(text) =>
contactUserInfoTextChangeHandler('city', text)
}
placeholder="شهر"
value={contactUserInfo.city}
editable={editUserConnectionInputs}
inputStyle={{
fontSize: 14,
top: 2,
color: '#838383',
}}
/>
<AppInputContainer
onChangeText={(text) =>
contactUserInfoTextChangeHandler('address', text)
}
value={contactUserInfo.address}
editable={editUserConnectionInputs}
placeholder="آدرس"
inputStyle={{
fontSize: 14,
top: 2,
color: '#838383',
}}
/>
{user.xaccount === userProfileInfo.id && (
<View style={styles.buttonContainer}>
{editUserConnectionInputs ? (
<AppButton
onPress={updateUserContactInfoHandler}
activeOpacity={0.7}
fontSize={16}
fontFamily={Fonts.iransansMedium}
textColor={Colors.primary_component_bg}
style={[styles.ctaButton, {backgroundColor: 'green'}]}>
ذخیره
</AppButton>
) : (
<AppButton
onPress={() =>
setEditUserConnectionInputs(!editUserConnectionInputs)
}
activeOpacity={0.7}
fontSize={16}
fontFamily={Fonts.iransansMedium}
textColor={Colors.primary_component_bg}
style={styles.ctaButton}>
ویرایش
</AppButton>
)}
</View>
)}
</View>
</ScrollView>
</ContactUserModal>
<Snackbar
duration={1500}
style={{
backgroundColor: 'green',
padding: 0,
}}
visible={snackbarVisible}
onDismiss={onDismissSnackBar}>
<View
style={{
height: 20,
width: ScreenSize.width - 50,
}}>
<Text style={{color: 'white'}}>اطلاعات با موفقیت به ثبت رسید</Text>
</View>
</Snackbar>
<ProfileCommentsListModal
{...{modalVisibilityHandler: profileCommentVisibilityHandler}}
isVisible={profileCommentsVisible}>
<KeyboardAvoidingView>
<View style={[styles.contentWrapperProfileComments]}>
<View style={{paddingBottom: 58}}>
<FlatList
ListEmptyComponent={<Text> nothing here </Text>}
data={accountCommentList}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => {
return (
<AppUserPostComment {...{item, registerCommentLike}} />
);
}}
/>
</View>
<AppCommentInput
{...{
registerCommentAccount,
commentAccountText,
setCommentAccountText,
}}
/>
</View>
</KeyboardAvoidingView>
</ProfileCommentsListModal>
</AppScreen>
);
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
},
contentWrapper: {
height: ScreenSize.height,
width: '100%',
backgroundColor: 'white',
borderRadius: 10,
},
contentWrapperProfileComments: {
height: '100%',
width: '100%',
backgroundColor: 'white',
borderRadius: 10,
},
closeButtonContainer: {
height: 50,
width: '100%',
justifyContent: 'center',
},
closeIcon: {
fontSize: 30,
color: Colors.secondary_text,
marginLeft: 10,
},
titleContainer: {
padding: 5,
marginBottom: 20,
},
buttonContainer: {
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
width: '100%',
bottom: 20,
},
ctaButton: {
backgroundColor: Colors.primary_text,
width: '90%',
borderRadius: 4,
height: 45,
},
});
so can u add results from logcat? because self-closing apps because something wrong, like component cant rendering completely
Im using: react-native, expo,
react-native-gesture-handler/Swipeable
here is a code of screen https://github.com/ArturScwaiberov/react-native-dental-app/blob/master/screens/HomeScreen.js
In my app there is list of appointments rendered by SectionList.
In renderItem I created two buttons, one of them is to delete item.
So, I cant understand how should I pass appointment ID to renderRightActions to delete or edit this current appointment.. please help me find out the solution!
Here is my HomeScreen code review:
const HomeScreen = ({ navigation }) => {
const [data, setData] = useState(null)
const [refreshing, setRefreshing] = useState(false)
const fetchAppointments = () => {
setRefreshing(true)
appointmentsApi
.get()
.then(({ data }) => {
setData(data.message)
setRefreshing(false)
})
.catch((e) => {
setRefreshing(false)
console.log(e)
})
}
useEffect(fetchAppointments, [])
const removeAppointment = (id) => {
console.log(id)
const result = data.map((group) => {
group.data = group.data.filter((item) => item._id !== id)
return group
})
setData(result)
//appointmentsApi.remove(id)
}
renderRightAction = (text, color, x, progress) => {
const trans = progress.interpolate({
inputRange: [0, 1],
outputRange: [x, 0],
})
const pressHandler = () => {
if (text === 'pencil') {
alert('hey')
} else {
//but how to get over here the ID of item from SectionList?
removeAppointment(id)
}
}
return (
<Animated.View style={{ flex: 1, transform: [{ translateX: trans }] }}>
<RectButton
style={{
alignItems: 'center',
flex: 1,
justifyContent: 'center',
backgroundColor: color,
}}
onPress={pressHandler}
>
<ActionText>
<Octicons name={text} size={24} color='white' />
</ActionText>
</RectButton>
</Animated.View>
)
}
renderRightActions = (progress) => (
<RightButtonsHandler>
{renderRightAction('pencil', '#B4C1CB', 160, progress)}
{renderRightAction('trashcan', '#F85A5A', 80, progress)}
</RightButtonsHandler>
)
return (
<Container>
<SectionList
style={{ paddingLeft: 20, paddingRight: 20 }}
sections={data}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => (
<Swipeable renderRightActions={renderRightActions} friction={2}>
<Appointment navigation={navigation} item={item} />
</Swipeable>
)}
renderSectionHeader={({ section: { title } }) => <SectionTitle>{title}</SectionTitle>}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={fetchAppointments} />}
/>
<PluseButton
style={{
shadowColor: '#2A86FF',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.3,
shadowRadius: 4.65,
elevation: 8,
}}
onPress={() => navigation.navigate('AddPatient')}
>
<Ionicons name='ios-add' size={32} color='white' />
</PluseButton>
</Container>
)
}
You only need to pass the item id as a function param.
renderRightActions={(progress) => renderRightActions(progress, item.id)}
I made all changes. Try this code:
const HomeScreen = ({ navigation }) => {
const [data, setData] = useState(null)
const [refreshing, setRefreshing] = useState(false)
const fetchAppointments = () => {
setRefreshing(true)
appointmentsApi
.get()
.then(({ data }) => {
setData(data.message)
setRefreshing(false)
})
.catch((e) => {
setRefreshing(false)
console.log(e)
})
}
useEffect(fetchAppointments, [])
const removeAppointment = (id) => {
console.log(id)
const result = data.map((group) => {
group.data = group.data.filter((item) => item._id !== id)
return group
})
setData(result)
//appointmentsApi.remove(id)
}
renderRightAction = (text, color, x, progress, id) => {
const trans = progress.interpolate({
inputRange: [0, 1],
outputRange: [x, 0],
})
const pressHandler = () => {
if (text === 'pencil') {
alert('hey')
} else {
// but how to get over here the ID of item from SectionList?
removeAppointment(id) // its simple! :)
}
}
return (
<Animated.View style={{ flex: 1, transform: [{ translateX: trans }] }}>
<RectButton
style={{
alignItems: 'center',
flex: 1,
justifyContent: 'center',
backgroundColor: color,
}}
onPress={pressHandler}
>
<ActionText>
<Octicons name={text} size={24} color='white' />
</ActionText>
</RectButton>
</Animated.View>
)
}
renderRightActions = (progress, id) => (
<RightButtonsHandler>
{renderRightAction('pencil', '#B4C1CB', 160, progress, id)}
{renderRightAction('trashcan', '#F85A5A', 80, progress, id)}
</RightButtonsHandler>
)
return (
<Container>
<SectionList
style={{ paddingLeft: 20, paddingRight: 20 }}
sections={data}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => (
<Swipeable renderRightActions={(progress) => renderRightActions(progress, item.id)} friction={2}>
<Appointment navigation={navigation} item={item} />
</Swipeable>
)}
renderSectionHeader={({ section: { title } }) => <SectionTitle>{title}</SectionTitle>}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={fetchAppointments} />}
/>
<PluseButton
style={{
shadowColor: '#2A86FF',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.3,
shadowRadius: 4.65,
elevation: 8,
}}
onPress={() => navigation.navigate('AddPatient')}
>
<Ionicons name='ios-add' size={32} color='white' />
</PluseButton>
</Container>
)
}
I'm getting activity indicator after 20 posts as the offset number is set to 20 and after each scroll its loading more content but I want it to stop loading (Activity Indicator) when reached at the end of the data and there is no data to fetch.
Here is all the default states:
this.state = {
data: [],
dataProvider: new DataProvider((item1, item2) => {
return item1.ID !== item2.ID;
}),
isLoading: false,
};
Here is the render of the component:
render(){
if( !this.state.data.length ) {
return(
<ActivityIndicator
style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}
size='large'
color='#0A80FE'
/>
);
}
return(
<>
<View style={{flex:1}}>
<RecyclerListView
style={{ flex: 1 }}
onEndReached={() => {
this.getData();
//(ignore this as this is required for recyclerlist view)
this.setState({});
}}
onEndReachedThreshold={1000}
dataProvider={this.state.dataProvider}
rowRenderer={this.renderItem}
renderFooter={this.renderFooter}
/>
</View>
</>
);
Here is the getData function:
getData = () => {
if(this.state.isLoading){
return;
}
this.setState({
isLoading: true
});
const url = 'some url?offset=' + this.state.data.length;
fetch(url).then((response) => response.json())
.then((responseJson) => {
this.setState({
data: this.state.data.concat(responseJson.posts),
dataProvider: this.state.dataProvider.cloneWithRows(
this.state.data.concat(responseJson.posts)
),
})
})
.catch((error) => {
console.error(error);
}).finally(() => {
this.setState({
isLoading: false,
})
})
}
Here's renderItem function:
renderItem = (type, item ) => (
<ListItem
containerStyle={{height: 120}}
title={item.title}
subtitle={item.author.name}
leftAvatar={avatar}
bottomDivider
/>
);
And here is renderFooter function:
renderFooter = () => {
return !this.isLoading
? <ActivityIndicator
style={{ marginVertical: 10 }}
size='large'
color='#0A80FE'
/>
: <View style={{ height: 60 }}>
<Text style={{color: '#ccc', textAlign: 'center', paddingVertical: 10,}}> You've reached at the end of the posts.</Text>
</View>;
};
renderFooter always sets to loading even if I reached at the end of the posts resulting in an unending activity indicator
I am having trouble getting an 'nested' FlatList to scroll. I have disabled the parent FlatList scrolling by passing scrollEnabled = {false} but that had no effect. I think the parent FlatList is intercepting the scroll events and I have not been able to find anything online that could be a possible solution.
export const createStyles = () => {
return {
baseStyles: {
height: 70,
overflow: 'hidden',
width: 200,
paddingHorizontal: 5,
borderWidth: 2,
borderColor: StyleConst.colors.grey.lighter3,
borderRadius: 3,
},
};
};
export const createItemStyle = (index, len) => {
console.log(index, len);
if (index !== len - 1) {
return {
borderBottomColor: StyleConst.colors.grey.lighter2,
borderBottomWidth: 1,
width: 150,
}
}
}
export class Dropdown extends Component {
static propTypes = {
data: arrayOf(object),
handleChange: func,
valueExtractor: func.isRequired,
}
renderItem = (item) => {
return (
<TouchableWithoutFeedback onPress={() => this.props.handleChange(this.props.valueExtractor(item))}>
<View key = {item.id}>
<Text>
{item.item.value}
</Text>
</View>
</TouchableWithoutFeedback>
)
}
render() {
const styles = createStyles();
return (
<View style={styles.baseStyles}>
<FlatList
data={this.props.data}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
onScrollBeginDrag={() => console.log("start")}
/>
</View>
)
}
}
"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".