Error : undefined is not an object (evaluating 'source') - javascript

I'm using WordPress API in my react native app. Everything is working fine except my single post screen. I'm requesting post data, updating state, and rendering data from the state. My app was crashing after going on this screen. I spend hours and I've found out what's the problem however don't how to fix it.
My Screen Code -
import React, { useState, useEffect } from "react";
import {
ActivityIndicator,
Text,
View,
ScrollView,
Image,
useWindowDimensions,
} from "react-native";
import HTMLView from "react-native-htmlview";
import globalStyles from "../constant/globalStyle";
import axios from "axios";
import moment from "moment";
import { Colors } from "../constant/colors";
const PostDetail = ({ route }) => {
const { postId } = route.params;
const [postData, setPostData] = useState({});
const [loaded, setLoaded] = useState(false);
const [source, setSource] = useState("");
useEffect(() => {
const fetchData = async () => {
await axios
.get(
`https://bachrasouthpanchayat.in/wp-json/wp/v2/posts/${postId}?embed=true`
)
.then((response) => {
setPostData(response.data);
setSource(response.data.content.rendered);
setLoaded(true);
})
.catch((error) => console.log(error, error.message));
};
fetchData();
}, []);
const { width } = useWindowDimensions();
return (
<ScrollView
style={{
...globalStyles.container,
backgroundColor: Colors.BACKGROUND_SCREEN,
}}
>
{loaded ? (
<>
<Image
style={{
width: "100%",
height: 250,
}}
source={{ uri: postData.jetpack_featured_media_url }}
resizeMode="cover"
/>
<Text style={globalStyles.primaryHeading}>
{postData.title.rendered}
</Text>
<View style={{ paddingHorizontal: 18 }}>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 10,
}}
>
<Text
style={{
...globalStyles.date,
fontSize: 20,
fontWeight: "700",
}}
>
{moment(postData.date).format("LL")}
</Text>
</View>
<HTMLView
stylesheet={{
p: { fontSize: 20, color: "white", textAlign: "justify" },
h1: {
color: "white",
fontSize: 32,
},
h2: {
color: "white",
fontSize: 28,
},
figure: {
backgroundColor: "red",
width: "100% !important",
height: "100% !important",
},
img: {
width: "100% !important",
backgroundColor: "green",
},
}}
value={source}
imagesMaxWidth={width}
/>
</View>
</>
) : (
<View
style={{
alignItems: "center",
justifyContent: "center",
}}
>
<ActivityIndicator size={60} color={Colors.PRIMARY_GREEN} />
</View>
)}
</ScrollView>
);
};
export default PostDetail;
I've used a bunch of console log statements and found out that "when useEffect runs the request is sent to API but it didn't stay on that line of code to receive a response, instead remaining lines of codes are executed and that's why I was getting the undefined error". However, the state gets updated after some time but I still get the error.
I have used async & await but don't know why all those remaining lines of codes ran before getting a response.

When this page is rendered for the first time, your postData is an empty object:
const [postData, setPostData] = useState({});
and at this point of time you try to access it's children (but it doesn't have them yet):
<Text style={globalStyles.primaryHeading}>
{postData.title.rendered} // here you access the empty object
</Text>
Then the useEffect is called which populates that postData
The solution (just add the question mark after postData.title?.rendered):
<Text style={globalStyles.primaryHeading}>
{postData.title?.rendered}
</Text>
The better solution is to show loading until you have not fetched the component:
if(postData.title?.rendered)
return <ActivityIndicator size={24} color={"#424242"} />
return (
<ScrollView
style={{
...globalStyles.container,
backgroundColor: Colors.BACKGROUND_SCREEN,
}}
>
{loaded ? (
<>
<Image
style={{
width: "100%",
height: 250,
}}
source={{ uri: postData.jetpack_featured_media_url }}
resizeMode="cover"
/>
<Text style={globalStyles.primaryHeading}>
{postData.title.rendered}
</Text>
<View style={{ paddingHorizontal: 18 }}>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 10,
}}
>
<Text
style={{
...globalStyles.date,
fontSize: 20,
fontWeight: "700",
}}
>
{moment(postData.date).format("LL")}
</Text>
</View>
<HTMLView
stylesheet={{
p: { fontSize: 20, color: "white", textAlign: "justify" },
h1: {
color: "white",
fontSize: 32,
},
h2: {
color: "white",
fontSize: 28,
},
figure: {
backgroundColor: "red",
width: "100% !important",
height: "100% !important",
},
img: {
width: "100% !important",
backgroundColor: "green",
},
}}
value={source}
imagesMaxWidth={width}
/>
</View>
</>
) : (
<View
style={{
alignItems: "center",
justifyContent: "center",
}}
>
<ActivityIndicator size={60} color={Colors.PRIMARY_GREEN} />
</View>
)}
</ScrollView>

Related

Async Storage movie watchlist in React Native

its my first time i create an APP for my study project , i created app of database of movies using TMDB API , and it remain one last step to finish my project , and it's creating WatchList or Plan to Watch , and i have no idea how to make it . please can someone who have idea of how to create it with Async Storage or anything to save watchlist, and where to add it?
I will put my code
this code of Movie screen, and i need a touchableopacity that make save the movie into favorite component
import React, {useEffect, useState} from 'react';
import {
View,
Text,
Image,
Dimensions,
StyleSheet,
Linking,
ImageBackground,
TouchableOpacity,
ScrollView,
FlatList
} from 'react-native';
import {IMAGE_POSTER_URL} from '../config';
import {GET} from '../../Services/API';
import Loader from '../Components/Loader';
import Constants from '../Components/Constants';
import TrendingMovies from '../Components/TrendingMovies';
import TrendingPeople from '../Components/TrendingPeople';
import { createStackNavigator } from "#react-navigation/stack";
import PeopleDetails from '../Components/PeopleDetails.js';
import { LinearGradient } from 'expo-linear-gradient';
import {POSTER_IMAGE} from '../config';
import AsyncStorage from '#react-native-async-storage/async-storage';
const deviceHeight = Dimensions.get('window').height;
const deviceWidth = Dimensions.get('window').width;
const MovieDetails = props => {
const [loading, setLoading] = useState(true);
const [details, setDetails] = useState();
const [favourites, setFavourites] = useState([]);
useEffect(() => {
const getDetails = async () => {
const data = await GET(`/movie/${props.route.params.movieId}`);
setDetails(data);
setLoading(false);
};
getDetails();
}, []);
useEffect(() => {
const getVideo = async () => {
const results = await GET(`/movie/${props.route.params.movieId/videos}`);
setDetails(results);
setLoading(false);
};
getVideo();
}, []);
const getGenre = () => {
return details.genres.map(genre => (
<View >
<Text style={styles.genre}>{genre.name}</Text>
</View>
));
};
return (
<View style={styles.sectionBg}>
{loading ? (
<Loader />
) : (
<View style={{ flex: 1 }} >
<Image
source={{uri: `${IMAGE_POSTER_URL}${details.backdrop_path}`}}
style={styles.imageBg}
/>
<View style={{flexDirection:'row', justifyContent:'space-between', alignItems:'center', marginRight:15, marginTop:10}}>
<TouchableOpacity onPress={() => props.navigation.goBack()}
style={{ marginLeft:20, marginTop: 20, marginBottom:20}}
>
<Image
source={require("../../assets/icons/back.png")}
style={{ width:93/1.4 , height: 50/1.4 }} />
</TouchableOpacity>
<TouchableOpacity
style={{ marginLeft:20, marginTop: 20, marginBottom:20}}
>
<Image
source={require("../../assets/icons/nolicked.png")}
style={{ width:256/5.7 , height: 252/5.7 }} />
</TouchableOpacity>
</View>
<ScrollView style={{ flex:1 ,}} >
<TouchableOpacity onPress={() => {
Linking.openURL('https://www.youtube.com/watch?v=${details.key}');
}}
style={{ marginTop:240, marginLeft: "70%",zIndex:1 }}
>
<Image
source={require("../../assets/icons/youtube.png")}
style={{ width: 75, height: 75}} />
</TouchableOpacity>
<ImageBackground
source={require("../../assets/icons/hmm.png")}
style={{ width:'100%' , height: '84%',zIndex:-1 ,marginTop: -60,marginBottom:20}} >
<View style={{ alignSelf: 'center' , marginTop:40 }} >
<View style={{flexDirection: 'row' }} >
<Image
source={{uri: `${IMAGE_POSTER_URL}${details.poster_path}`}}
style={{ width: 150/1.2 , height: 220/1.2 , borderRadius: 20, marginTop: 40 , marginLeft: 20, zIndex:1 }} />
<View style= {{flexDirection:'column' , }} >
<Text style={styles.detailsMovieTitle}>{details.title}</Text>
<View style={{flexDirection: 'row' , alignItems: 'center', marginLeft: 15 , backgroundColor:'orange' , width:70, marginVertical: 5, borderRadius:10}} >
<Image
source={require("../../assets/icons/star.png")}
style={{ width: 20, height: 20 , marginLeft: 5, marginVertical:8}} />
<Text style= {{color:'#20222A' , fontSize: 18, marginLeft: 6, fontWeight:'bold' ,marginRight: 15}} >{details.vote_average}</Text>
</View>
<View style={{ flexDirection: 'row', width: 80 ,marginTop:25, marginLeft:20}}>
{getGenre()}
</View>
<Text style={{marginLeft: 14,
marginHorizontal:-5,
marginVertical:5,
fontWeight:'bold',
color: '#C3C3C3',
fontSize: 10,}}>{details.runtime} Minutes</Text>
<Text style={{marginLeft: 14,
marginHorizontal:-5,
color: '#C3C3C3',
fontWeight:'bold',
fontSize: 10,}}> Release Date: {details.release_date} </Text>
</View>
</View>
<View style={{marginLeft:15, zIndex:1 , marginBottom:50, marginLeft:20}} >
<Text style={{color:'white', fontSize:16, fontWeight:'bold' , marginBottom: 1,marginLeft: -5}} > Overview </Text>
<Text style={{color: 'white',
fontSize: 12, width:330, marginBottom:15}}>{details.overview}</Text>
<TrendingPeople
title="Actors"
navigation={props.navigation}
url={`/movie/${props.route.params.movieId}/credits`}
isForPage="details"
/>
<View style={{marginLeft: -15, marginTop:10, }} >
<TrendingMovies
title="SIMILAR MOVIES"
navigation={props.navigation}
url={`/movie/${props.route.params.movieId}/similar`}
/>
</View>
</View>
</View>
</ImageBackground>
</ScrollView>
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
sectionBg: {
backgroundColor: Constants.baseColor,
height: deviceHeight,
flex:1
},
trendingPeopleImage: {
height: 70,
width: 70,
borderRadius: 500,
},
trendingPeopleName: {
width: 60,
color: Constants.textColor,
fontSize: 12,
textAlign: 'center',
marginTop: 10,
},
trendingPeopleContainer: {
margin: 10,
},
heading: {
fontSize: 12,
color: 'white',
margin: 10,
fontWeight:'bold'
},
posterImage: {
height: 800,
width: 150,
borderRadius: 10,
},
movieTitle: {
color: Constants.textColor,
width: 150,
textAlign: 'center',
marginTop: 5,
fontSize: 20,
fontWeight:'bold'
},
imageBg: {
position: 'absolute', top:0, left:0 ,
width: deviceWidth,
height: 400,
opacity: 0.8
},
detailsMovieTitle: {
fontSize: 20,
width: 180,
fontWeight:'bold',
color: 'white',
marginLeft:15,
marginTop: 35
},
linkContainer: {
backgroundColor: Constants.secondaryColor,
borderRadius: 100,
padding: 10,
width: 45,
marginLeft: 20,
marginTop: -20,
},
overview: {
color: 'white',
marginHorizontal: 10,
textAlign: 'justify',
fontSize: 16,
},
details: {
color: 'white',
fontSize: 11,
marginLeft: 10,
fontWeight: 'bold',
},
detailsContainer: {
display: 'flex',
marginLeft:15
},
genreContainer: {
borderColor: Constants.textColor,
paddingHorizontal: 0,
},
genre: {
width: 50,
marginHorizontal:-5,
fontWeight:'bold',fontWeight:'bold',
color: '#C3C3C3',
marginRight:4,
fontSize: 9,
},
image : {
height: 100,
width: 180,
borderRadius: 15,
paddingHorizontal: 5,
marginTop: 10,
marginLeft: 20
},
Text: {
marginLeft: 20,
color: 'white',
fontWeight: 'bold',
fontSize: 20
},
title: {
marginTop: 10,
width: 150,
textAlign: 'center',
color: 'white',
fontWeight: 'bold',
fontSize: 12
}
});
export default MovieDetails;```
First you create a favorite state
const [ favorites, setFavorites ] = useState([]);
Then you use useEffect to load the favorite from async storage.
const getData = async () => {
try {
const value = await AsyncStorage.getItem('#storage_key')
if(value !== null) {
setFavorites(JSON.parse('#storage_key'));
}
} catch(e) {
// error reading value
}
}
useEffect(() => {
getData();
}, [])
Then you create a touchable component to add to favorites.
<TouchableOpacity onPress={() => addToFavorites('some_id')}>
Add to Favorites
</TouchableOpacity>
Create the add to favorite function
const addToFavorites = async (id) => {
await AsyncStorage.setItem('#storage_key', JSON.stringify([...favorites, id]))
setFavorites([...favorites, id]);
}
More on Asyncstorage usage here - https://react-native-async-storage.github.io/async-storage/docs/usage

How to set Json array from flatlist in react native

I am new in react native . i am get data from asynstorage. how to set that data from flatlist. my problem is can not set value properly in flatlist data. herewith below attached the JSon value and my code. am try to long time but no idea for that please help me. please
I get data to asystorage value Json Data
{
"users":[
{
"title":"Opna Women's Short Sleeve Moisture",
"image":"https://fakestoreapi.com/img/51eg55uWmdL._AC_UX679_.jpg",
"id":19
},
{
"title":"DANVOUY Womens T Shirt Casual Cotton Short",
"image":"https://fakestoreapi.com/img/61pHAEJ4NML._AC_UX679_.jpg",
"id":20
}
]
}
Main code.
import React, { useState } from 'react';
import { SafeAreaView, StyleSheet, View, Text, Image, Alert, TouchableOpacity } from 'react-native';
import { FlatList } from 'react-native-gesture-handler';
import Icon from 'react-native-vector-icons/MaterialIcons';
import COLORS from '../../consts/colors';
import foods from '../../consts/foods';
import { PrimaryButton } from '../components/Button';
import AsyncStorage from '#react-native-async-storage/async-storage';
const CartScreen = ({ navigation }) => {
const [value, setvalue] = useState(true);
AsyncStorage.getItem('storeddata')
.then((value) => {
setvalue(value)
console.log("saveddata", value);
}
)
const CartCard = ({ item }) => {
console.log("itemlist", item);
return (
<View style={style.cartCard}>
<Image source={{ uri: item.image }} style={{ height: 80, width: 80 }} />
<View
style={{
height: 100,
marginLeft: 10,
paddingVertical: 20,
flex: 1,
}}>
<Text style={{ fontWeight: 'bold', fontSize: 16 }}>{item.title}</Text>
<Text style={{ fontSize: 13, color: COLORS.grey }}>
{item.title}
</Text>
<Text style={{ fontSize: 17, fontWeight: 'bold' }}>${item.id}</Text>
</View>
<View style={{ marginRight: 20, alignItems: 'center' }}>
<Text style={{ fontWeight: 'bold', fontSize: 18 }}>5</Text>
<View style={style.actionBtn}>
<Icon name="remove" size={25} color={COLORS.white} />
<Icon name="add" size={25} color={COLORS.white} />
</View>
</View>
</View>
);
};
return (
<SafeAreaView style={{ backgroundColor: COLORS.white, flex: 1 }}>
<View style={style.header}>
<Icon name="arrow-back-ios" size={28} onPress={navigation.goBack} />
<Text style={{ fontSize: 20, fontWeight: 'bold' }}>Cart</Text>
</View>
<FlatList
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: 80 }}
data={value}
renderItem={({ item }) => <CartCard item={item} />}
ListFooterComponentStyle={{ paddingHorizontal: 20, marginTop: 20 }}
ListFooterComponent={() => (
<View>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
marginVertical: 15,
}}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>
Total Price
</Text>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>$50</Text>
</View>
<View style={{ marginHorizontal: 30 }}>
<PrimaryButton title="CHECKOUT" onPress={() => navigation.navigate('Checkout')} />
</View>
</View>
)}
/>
</SafeAreaView>
);
};
const style = StyleSheet.create({
header: {
paddingVertical: 20,
flexDirection: 'row',
alignItems: 'center',
marginHorizontal: 20,
},
cartCard: {
height: 100,
elevation: 15,
borderRadius: 10,
backgroundColor: COLORS.white,
marginVertical: 10,
marginHorizontal: 20,
paddingHorizontal: 10,
flexDirection: 'row',
alignItems: 'center',
},
actionBtn: {
width: 80,
height: 30,
backgroundColor: COLORS.primary,
borderRadius: 30,
paddingHorizontal: 5,
flexDirection: 'row',
justifyContent: 'center',
alignContent: 'center',
},
});
export default CartScreen;
Flatlist data props take an array as an input Official doc
you are getting an object from local storage you have to pass an array to it
local storage save string value so you have to do JSON.stringify() it when you store data in it and JSON.parse() it when getting data from local storage
local storage link
If your value have that above JSON data stored then try to pass prop like:
<FlatList
...
data={value.users}
...
/>
Hope this works for you.

React Native Component Exception <Text strings must be rendered within a Text component>

I am trying to build a react native application with the expo, First I try to build this application with my chrome web browser, it was worked without any issue after that I try to test the application with my android device and I'm getting an exception - "Text strings must be rendered within a <Text> component" HomeScreen.js files. I have no idea why this happened. My code as follows,
/*This is an Example of Grid View in React Native*/
// import * as React from "react";
import React from 'react';
import { Image, FlatList, StyleSheet, View, Text, TouchableOpacity, TextInput } from 'react-native';
import { COLORS } from '../../asserts/Colors/Colors';
import { CATEGORIES } from '../../asserts/mocks/itemListData';
import CategoryGridTitle from '../components/HomeGridTile';
import { HeaderButtons, Item } from 'react-navigation-header-buttons';
import { HeaderButton } from '../components/HeaderButton';
import HomeScreenButton from '../components/HomeScreenButton';
//import all the components we will need
const renderTopBarItems = (topBarItems) => {
return (
<TouchableOpacity
style={styles.topBar}>
<Text style={styles.textStyle}> {topBarItems.item.category} </Text>
</TouchableOpacity>
)
}
const HomeScreen = props => {
const renderGridItem = (itemData) => {
return <CategoryGridTitle
title={itemData.item.title}
image={itemData.item.image}
onSelect={() => {
props.navigation.navigate({
routeName: 'PaymentHandlerScreen',
params: {
categoryId: itemData.item.id
}
});
}} />;
}
// const [images, setImages] = React.useState(picsumImages);
return (
<View style={styles.mainBody}>
<View style={styles.searchContainer}>
<TextInput
placeholder='Search'
style={styles.formField}
placeholderTextColor={'#888888'}
/>
<TouchableOpacity onPress={() => props.navigation.navigate('BarCodeScannerScreen')}
style={styles.saveFormField}>
<Image
source={require('../../../images/barcode.png')}
style={{
width: '100%',
height: '30%',
resizeMode: 'contain',
alignContent: 'center',
}}
/> </TouchableOpacity>
</View>
<View style={styles.tabBar}>
<FlatList
horizontal
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
keyExtractor={(item, index) => item.id}
data={CATEGORIES}
renderItem={renderTopBarItems} />
</View>
<FlatList
keyExtractor={(item, index) => item.id}
data={CATEGORIES}
renderItem={renderGridItem}
numColumns={3} />
<HomeScreenButton style={styles.buttonView} />
</View>
);
};
HomeScreen.navigationOptions = navigationData => {
return {
headerTitle: 'Tickets',
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='profile'
iconName='ios-star'
onPress={() => {
console.log('profile clicked');
}} />
<Item
title='more'
iconName='md-more'
onPress={() => {
console.log('more clicked');
}} />
</HeaderButtons>
)
};
};
export default HomeScreen;
const styles = StyleSheet.create({
mainBody: {
flex: 1,
justifyContent: 'center',
backgroundColor: COLORS.background,
paddingTop: '3%',
},
searchContainer: {
flex: 1,
flexDirection: 'row',
},
tabBar: {
paddingBottom: '3%',
},
topBar: {
width: 150,
borderWidth: 1,
borderRadius: 20,
borderColor: COLORS.primary_blue,
padding: '5%',
marginLeft: '5%',
},
textStyle: {
color: COLORS.primary_blue,
textAlign: 'center',
fontWeight: 'bold',
fontSize: 14,
},
formField: {
flex: 4,
borderWidth: 1,
padding: '4%',
marginLeft: '2%',
borderRadius: 10,
borderColor: COLORS.gray,
backgroundColor: COLORS.gray,
fontSize: 15,
height: '35%',
},
saveFormField: {
flex: 0.5,
justifyContent: 'space-between',
alignItems: 'center',
margin: 10,
},
buttonView: {
position: 'absolute',
bottom: 0,
left: 0,
},
});
Thank you.
I ran into this error a couple of times. RN doesn't like extra spaces in tags. try removing the spaces before and after {topBarItems.item.category}
<Text style={styles.textStyle}>{topBarItems.item.category}</Text>

How do i set and get the different states and inputs from a flatlist with multiple objects

ok i got this flatlist contained within a flatlist, and the flatlist from inside, contains dropdowns, inputs, and buttons that i need to capture which data i'm getting from them, and of course save the new data based on what i selected, typed in or clicked, i'm not entirely sure how to do this, this is the code for flatlist
<FlatList
extraData = {this.state}
data = {this.state.dataSource}
renderItem = {({item, index}) => {
return (
<View style={{marginTop: hp('2%')}}>
<TouchableOpacity style={{
alignSelf: 'center',
width: wp('95.7%'),
height: hp('10%'),
backgroundColor: "#ffffff",
}} onPress={() => this.toggleExpanded()}>
<View style={{flex: 1, flexDirection: 'row', justifyContent: 'space-between'}}>
<View style={{flexDirection: 'column', justifyContent: 'center'}}>
<Text numberOfLines={2} ellipsizeMode="tail" style={{
marginLeft: wp('5%'),
width: wp('55%'),//wp('38.2%'), //151
//height: hp('2%'), //19
fontSize: rfv(16),
fontWeight: "500",
fontStyle: "normal",
textAlign: "left",
color: "#707070"
}}>{item.Title}</Text>
<Text style={{
marginLeft: wp('5%'),
fontSize: rfv(14),
fontWeight: "normal",
fontStyle: "normal",
textAlign: "left",
color: "#c4c4c4"
}}>{`ID ${item.Id} - ${item.Cliente}`}</Text>
{
!item.IsFavorite ?
<Text style={{
marginLeft: wp('5%'),
fontSize: rfv(14),
fontWeight: "normal",
fontStyle: "italic",
textAlign: "left",
color: "#c4c4c4"
}}>No favorito</Text>
: null
}
</View>
<View style={{flexDirection: 'column', justifyContent: 'center'}}>
<View style={{flexDirection: 'row', marginRight: wp('3.4%')}}>
<Text style={{
fontSize: rfv(18),
fontWeight: "300",
fontStyle: "normal",
textAlign: "right",
color: "#707070"}}>{`${0}h`}</Text>
<Image style={{marginTop: hp('1%'), marginLeft: wp('3.7%')}} source={this.state.isCollapsed ? Images.expandible : Images.collapsible}/>
</View>
</View>
</View>
</TouchableOpacity>
<Collapsible style={{
alignSelf: 'center',
width: wp('95.7%'),
backgroundColor: "#ffffff",}} collapsed={this.state.isCollapsed}>
<FlatList
//extraData = {this.state}
data = {item.ListReportHistoryResponse}//{DataManager.FavoriteList[moment(this.state.selectedDate).format('YYYY-MM-DD')]}
renderItem = {({ item, index }) => {
return (
<View>
<View style={{flexDirection: 'row'}}>
<Text style={{
marginLeft: wp('5%'),
fontSize: rfv(14),
fontWeight: "normal",
fontStyle: "normal",
textAlign: "left",
color: "#717171"
}}>Etapa</Text>
<Text style={{
marginLeft: wp('42.5%'),
fontSize: rfv(14),
fontWeight: "normal",
fontStyle: "normal",
textAlign: "left",
color: "#717171"
}}>Horas</Text>
</View>
<View style={{flexDirection: 'row'}}>
<ModalDropdown
adjustFrame={style => {
style.top =(Platform.OS === 'ios' ? style.top : style.top - StatusBar.currentHeight);
return style;
}}
dropdownTextStyle={styles.dropdownTextStyle}
dropdownTextHighlightStyle={styles.dropdownTextHighlightStyle}
dropdownStyle={styles.dropdownStageStyle}
defaultValue={item.Etapa}
style={styles.dropStageStyle}
textStyle={{
padding: 0,
margin: 0,
fontSize: rfv(16),
paddingVertical: hp('1.2%'),
fontWeight: 'normal',
fontStyle: 'normal',
textAlign: 'left',
color: item.Etapa /*item.ListReportHistoryResponse[index2].Etapa*/ != 'Selecciona una etapa' ? '#1a1a1a' : '#c4c4c4',
}}
//onSelect={(index, value) => this.setState({SeleccionClientes: value})}
//options={Object.keys(this.state.items)}
onSelect={(index, value) => this.setState({SeleccionClientes: value})}
options={DataManager.ListEtapa}
/>
<View style={styles.InputContainerHours}>
<Text style={styles.InputTextHours}>{item.HorasTrabajadas}</Text>
</View>
<TouchableOpacity style={{marginTop: hp('0.5%'), marginLeft: wp('5.5%')}} onPress={() => this.props.onSubstract}>
<Image source={Images.menos_hora}/>
</TouchableOpacity>
<TouchableOpacity style={{marginTop: hp('0.5%'), marginLeft: wp('2%')}} onPress={() => this.props.onAdd}>
<Image source={Images.mas_hora}/>
</TouchableOpacity>
</View>
<Text style={{
fontSize: rfv(14),
marginLeft: wp('5%'),
fontWeight: "normal",
fontStyle: "normal",
textAlign: "left",
color: "#717171"
}}>Observaciones</Text>
<Input
autoCapitalize="none"
maxLength={100}
inputContainerStyle={styles.InputContainerComentarioOnBlur}
containerStyle={styles.InputComentario}
inputStyle={styles.InputTextHoursRInput}
placeholderTextColor={'#c4c4c4'}
placeholder="(Opcional)"
onChangeText={value => this.setState({})}
/>
<TouchableOpacity style={{alignItems: 'flex-end', alignSelf: 'flex-end'}}>
<Text style={{
marginRight: wp('3.4%'),
marginBottom: hp('3%'),
fontSize: rfv(14),
fontWeight: "normal",
fontStyle: "normal",
textAlign: "left",
color: "#1062cc"
}}>Agregar etapa</Text>
</TouchableOpacity>
</View>
)}}/>
</Collapsible>
</View>
)
}}
/>
EDIT: updated with screenshot of the object displayed in flatlist, i still need help getting this to work
<View style={styles.InputContainerHours}>
<Text style={styles.InputTextHours}>{item.HorasTrabajadas}</Text>
</View>
<TouchableOpacity style={{marginTop: hp('0.5%'), marginLeft: wp('5.5%')}} onPress={() => this.props.onSubstract}>
<Image source={Images.menos_hora}/>
</TouchableOpacity>
<TouchableOpacity style={{marginTop: hp('0.5%'), marginLeft: wp('2%')}} onPress={() => this.props.onAdd}>
<Image source={Images.mas_hora}/>
</TouchableOpacity>
the onadd is supposed function that adds to item.HorasTrabajadas, 0.5 in addition to the current value, and the onsubstract is the same but substracts to the current value
and here is the object
edit 2: what if the api contains an object with data saved already, how do i edit that new data?
edit 3: i added a github project with the 3 most important parts of this project, https://github.com/Vasault/HourReg/tree/develop contains 3 files, the most important one HourRegistration.tsx
edit 4: unable to change data that already come from api
You can use the index of your array to identify the selected item, and when you have an index of every time you can update the item on every action according to your need.
Let suppose you have 10 items and on every item, you have to update the state you can get the item and index of that row and can update the state easily.
Here is a simple example that can clear your confusion.
And here is a snack with a working example
https://snack.expo.io/#waheed25/radarada
import React from 'react';
import { SafeAreaView, View, TextInput, FlatList, StyleSheet, Text } from 'react-native';
import Constants from 'expo-constants';
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
},
];
export default class App extends React.Component {
state={
observacionesInput: []
}
onTextObservacionesChange = (text, index) => {
var { observacionesInput } = this.state
let myArray = observacionesInput
myArray[index] = text
this.setState({observacionesInput: myArray });
}
toggleExpanded = () => {
this.setState({ isCollapsed: !this.state.isCollapsed });
};
render(){
console.log('state', this.state)
return (
<SafeAreaView>
<Text>Testing</Text>
<FlatList
data={DATA}
renderItem={({ item, index }) => {
return(
<View style={{ marginTop: 20}}>
<TextInput
style={{borderColor: 'gray', borderWidth: 1, height: 50,}}
autoCapitalize="none"
maxLength={100}
placeholderTextColor={'#c4c4c4'}
placeholder="(Opcional)"
onChangeText={value => this.onTextObservacionesChange(value, index)}
/>
</View>
)
}}
/>
</SafeAreaView>
);
}
}
you can make dynamic state by append parentIndex and childIndex to state to uniquely identify of item of child flatlist
you can use onChangeValue for save data into newDataSave object and getDataFromNewSave for get data from newDataSave
Note: you all saving data is saving in newDataSave object. you can console to check it and save into database.
onChangeValue = (key, value) => {
this.setState((prevState) => {
return {
newDataSave: {
...prevState.newDataSave,
[key]: value,
},
};
});
};
getDataFromNewSave = (key) => {
try {
return this.state.newDataSave[key];
} catch (error) {
return "";
}
};
<Input
value={this.getDataFromNewSave(
`input${parentIndex}${childIndex}`
)}
autoCapitalize="none"
maxLength={100}
inputContainerStyle={
styles.InputContainerComentarioOnBlur
}
containerStyle={styles.InputComentario}
inputStyle={styles.InputTextHoursRInput}
placeholderTextColor={"#c4c4c4"}
placeholder="(Opcional)"
onChangeText={(value) => {
this.onChangeValue(
`input${parentIndex}${childIndex}`,
value
);
}}
/>
Full Code:
import React, { Component } from "react";
import {
Text,
StyleSheet,
View,
FlatList,
TouchableOpacity,
Image,
TextInput,
} from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp,
} from "react-native-responsive-screen";
import Collapsible from "react-native-collapsible";
import ModalDropdown from "react-native-modal-dropdown";
const Input = TextInput;
const rfv = (value) => {
return value;
};
const DataManager = {
ListEtapa: ["a", "b", "c"],
};
const Images = {
collapsed: require("./assets/icons/app-icon.png"),
expandible: require("./assets/icons/app-icon.png"),
};
export default class App extends Component {
state = {
isCollapsed: false,
dataSource: [
{
Id: "1",
Title: "abc1",
Cliente: "abc1",
IsFavorite: false,
ListReportHistoryResponse: [
{ Etapa: "abc1", HorasTrabajadas: "abcabc1" },
{ Etapa: "cde1", HorasTrabajadas: "cdecde1" },
],
},
{
Id: "2",
Title: "abc2",
Cliente: "abc2",
IsFavorite: false,
ListReportHistoryResponse: [
{ Etapa: "abc2", HorasTrabajadas: "abcabc2" },
{ Etapa: "cde2", HorasTrabajadas: "cdecde2" },
],
},
],
newDataSave: {},
};
onChangeValue = (key, value) => {
this.setState((prevState) => {
return {
newDataSave: {
...prevState.newDataSave,
[key]: value,
},
};
});
};
getDataFromNewSave = (key) => {
try {
return this.state.newDataSave[key];
} catch (error) {
return "";
}
};
render() {
return (
<View>
<FlatList
extraData={this.state}
data={this.state.dataSource}
renderItem={({ item, index: parentIndex }) => {
return (
<View style={{ marginTop: hp("2%") }}>
<TouchableOpacity
style={{
alignSelf: "center",
width: wp("95.7%"),
height: hp("10%"),
backgroundColor: "#ffffff",
}}
onPress={() => this.toggleExpanded()}
>
<View
style={{
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
}}
>
<View
style={{
flexDirection: "column",
justifyContent: "center",
}}
>
<Text
numberOfLines={2}
ellipsizeMode="tail"
style={{
marginLeft: wp("5%"),
width: wp("55%"), //wp('38.2%'), //151
//height: hp('2%'), //19
fontSize: rfv(16),
fontWeight: "500",
fontStyle: "normal",
textAlign: "left",
color: "#707070",
}}
>
{item.Title}
</Text>
<Text
style={{
marginLeft: wp("5%"),
fontSize: rfv(14),
fontWeight: "normal",
fontStyle: "normal",
textAlign: "left",
color: "#c4c4c4",
}}
>{`ID ${item.Id} - ${item.Cliente}`}</Text>
{!item.IsFavorite ? (
<Text
style={{
marginLeft: wp("5%"),
fontSize: rfv(14),
fontWeight: "normal",
fontStyle: "italic",
textAlign: "left",
color: "#c4c4c4",
}}
>
No favorito
</Text>
) : null}
</View>
<View
style={{
flexDirection: "column",
justifyContent: "center",
}}
>
<View
style={{
flexDirection: "row",
marginRight: wp("3.4%"),
}}
>
<Text
style={{
fontSize: rfv(18),
fontWeight: "300",
fontStyle: "normal",
textAlign: "right",
color: "#707070",
}}
>{`${0}h`}</Text>
<Image
style={{
marginTop: hp("1%"),
marginLeft: wp("3.7%"),
}}
source={
this.state.isCollapsed
? Images.expandible
: Images.collapsible
}
/>
</View>
</View>
</View>
</TouchableOpacity>
<Collapsible
style={{
alignSelf: "center",
width: wp("95.7%"),
backgroundColor: "#ffffff",
}}
collapsed={this.state.isCollapsed}
>
<FlatList
//extraData = {this.state}
data={item.ListReportHistoryResponse} //{DataManager.FavoriteList[moment(this.state.selectedDate).format('YYYY-MM-DD')]}
renderItem={({ item, index: childIndex }) => {
return (
<View>
<View style={{ flexDirection: "row" }}>
<Text
style={{
marginLeft: wp("5%"),
fontSize: rfv(14),
fontWeight: "normal",
fontStyle: "normal",
textAlign: "left",
color: "#717171",
}}
>
Etapa
</Text>
<Text
style={{
marginLeft: wp("42.5%"),
fontSize: rfv(14),
fontWeight: "normal",
fontStyle: "normal",
textAlign: "left",
color: "#717171",
}}
>
Horas
</Text>
</View>
<View style={{ flexDirection: "row" }}>
<ModalDropdown
adjustFrame={(style) => {
style.top =
Platform.OS === "ios"
? style.top
: style.top - StatusBar.currentHeight;
return style;
}}
dropdownTextStyle={styles.dropdownTextStyle}
dropdownTextHighlightStyle={
styles.dropdownTextHighlightStyle
}
dropdownStyle={styles.dropdownStageStyle}
defaultValue={this.getDataFromNewSave(
`SeleccionClientes${parentIndex}${childIndex}`
)}
style={styles.dropStageStyle}
textStyle={{
padding: 0,
margin: 0,
fontSize: rfv(16),
paddingVertical: hp("1.2%"),
fontWeight: "normal",
fontStyle: "normal",
textAlign: "left",
color:
item.Etapa /*item.ListReportHistoryResponse[index2].Etapa*/ !=
"Selecciona una etapa"
? "#1a1a1a"
: "#c4c4c4",
}}
//onSelect={(index, value) => this.setState({SeleccionClientes: value})}
//options={Object.keys(this.state.items)}
onSelect={(i, value) =>
// this.setState({ SeleccionClientes: value })
this.onChangeValue(
`SeleccionClientes${parentIndex}${childIndex}`,
value
)
}
options={DataManager.ListEtapa}
/>
<View style={styles.InputContainerHours}>
<Text style={styles.InputTextHours}>
{item.HorasTrabajadas}
</Text>
</View>
<TouchableOpacity
style={{
marginTop: hp("0.5%"),
marginLeft: wp("5.5%"),
}}
onPress={() => this.props.onSubstract}
>
<Image source={Images.menos_hora} />
</TouchableOpacity>
<TouchableOpacity
style={{
marginTop: hp("0.5%"),
marginLeft: wp("2%"),
}}
onPress={() => this.props.onAdd}
>
<Image source={Images.mas_hora} />
</TouchableOpacity>
</View>
<Text
style={{
fontSize: rfv(14),
marginLeft: wp("5%"),
fontWeight: "normal",
fontStyle: "normal",
textAlign: "left",
color: "#717171",
}}
>
Observaciones
</Text>
<Input
value={this.getDataFromNewSave(
`input${parentIndex}${childIndex}`
)}
autoCapitalize="none"
maxLength={100}
inputContainerStyle={
styles.InputContainerComentarioOnBlur
}
containerStyle={styles.InputComentario}
inputStyle={styles.InputTextHoursRInput}
placeholderTextColor={"#c4c4c4"}
placeholder="(Opcional)"
onChangeText={(value) => {
this.onChangeValue(
`input${parentIndex}${childIndex}`,
value
);
}}
/>
<TouchableOpacity
style={{
alignItems: "flex-end",
alignSelf: "flex-end",
}}
>
<Text
style={{
marginRight: wp("3.4%"),
marginBottom: hp("3%"),
fontSize: rfv(14),
fontWeight: "normal",
fontStyle: "normal",
textAlign: "left",
color: "#1062cc",
}}
>
Agregar etapa
</Text>
</TouchableOpacity>
</View>
);
}}
/>
</Collapsible>
</View>
);
}}
/>
</View>
);
}
}
const styles = StyleSheet.create({});
SOLVED, in a much better and easier way
on every input, dropdown, etc. set a function and pass 3 parameters
onChangeText={value => this.onChangeTextObservaciones(parentIndex, childIndex, value)
parentIndex (the one from outside flatlist), childIndex (the one from inside), and the value you're editing (blank or not)
const onChangeTextObservaciones = (indexOutside, indexInside, value) => {
this.dataList[indexOutside].ListReportHistoryResponse[indexInside].Title = value;
this.setState({displayDay: this.dataList});
}
change the specific value from the specific element from that object, and change it with the new value, and lastly change the whole new object with the new value.
a friend of mine realize this

I can't re-render the FlatList after empty data?

I Get the data from firebase real-time and put it I FlatList and when I delete if from Console "Firebase" it's deleted from the List in screen very well but the last item in the Array "Data" couldn't be deleted I don't know why!
I use an onRefresh Props but not help me because we all know the DB is real-time and when we will add any item it's will be in the last without refresh it So it's not work with the last item too and just the loading stuck without re-render the FlatList
Although I use .once('value') when I get data from DB, refresh work but when I refresh after deleting the last item the loading refresh stuck and can't disappear the last item
so how can I solve this issue?
here is my code
import auth from '#react-native-firebase/auth';
import database from '#react-native-firebase/database';
import React, {Component} from 'react';
import {
Dimensions,
FlatList,
Image,
Text,
TouchableOpacity,
View,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
const {width} = Dimensions.get('window');
export default class PendingOrders extends Component {
constructor(props) {
super(props);
this.state = {
orders: [],
forceUpdate: true,
isFetching: false,
};
}
onRefresh = () => {
this.setState({isFetching: true}, () => this.getApiData());
};
getApiData = () => {
try {
const uid = auth().currentUser.uid;
const Orders = database().ref(`usersOrders/${uid}`);
Orders.on('value', snapshot => {
let orders = [];
snapshot.forEach(childSnapshot => {
if (childSnapshot.val().status == 'pending') {
orders.push({
buildingNumber: childSnapshot.val().buildingNumber,
service: childSnapshot.val().categoryName,
date: childSnapshot.val().date,
time: childSnapshot.val().time,
description: childSnapshot.val().problemDescription,
status: childSnapshot.val().status,
images: childSnapshot.val().Images,
});
this.setState({orders, forceUpdate: false, isFetching: false}, () =>
console.log(this.state.orders),
);
return;
}
});
});
} catch (err) {
console.log('Error fetching data: ', err);
}
};
componentDidMount() {
this.getApiData();
}
_listEmptyComponent = () => {
return (
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
<Image
style={{
width,
height: width * 0.7,
resizeMode: 'contain',
}}
source={require('../../assets/empty.png')}
/>
<Text
style={{
color: '#000',
marginVertical: 15,
textAlign: 'center',
fontSize: 20,
}}>
No item found
</Text>
</View>
);
};
render() {
console.log('is?', this.state.forceUpdate);
return (
<FlatList
showsVerticalScrollIndicator={false}
data={this.state.orders}
extraData={this.state.isFetching}
onRefresh={() => this.onRefresh()}
ListEmptyComponent={this._listEmptyComponent()}
refreshing={this.state.isFetching}
contentContainerStyle={{
flexBasis: '100%',
}}
renderItem={({item}) => {
return (
<TouchableOpacity
onPress={() =>
this.props.navigation.navigate('OrderDetailsScreen', {
service: item.service,
time: item.time,
date: item.date,
description: item.description,
images: item.images,
status: item.status,
})
}
style={{
margin: 15,
borderRadius: 10,
borderWidth: 1,
flexDirection: 'row',
borderColor: '#ddd',
}}>
<Image
style={{
borderRadius: 10,
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
width: 150,
height: 150,
}}
resizeMode="cover"
source={item.images[0]}
/>
<View
style={{
margin: 5,
marginLeft: 10,
justifyContent: 'space-evenly',
}}>
<Text
style={{
marginBottom: 5,
fontWeight: 'bold',
fontSize: 16,
marginTop: 5,
}}>
{item.service}
</Text>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
}}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
// paddingHorizontal: 5,
}}>
<Icon name="date-range" color="#AEACAC" size={20} />
<Text style={{paddingHorizontal: 5}}>{item.date}</Text>
</View>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 5,
}}>
<Icon name="access-time" color="#AEACAC" size={20} />
<Text style={{paddingHorizontal: 5}}>{item.time}</Text>
</View>
</View>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={{marginBottom: 5, width: 160, marginTop: 5}}>
{item.description}
</Text>
</View>
</TouchableOpacity>
);
}}
keyExtractor={(item, index) => index.toString()}
/>
);
}
}
The key for re activating flatlist is extraData. just change it's value to something else and it will trigger flatlist re rendering. for simplicity pass a boolean value to extraData. every time you want to re active the flatlist, just toggle the value of extraData

Categories