Slow image load/Pull images from storage - firebase - react native - reactjs

I am pulling data from my firestore DB, one of the fields on each DB item is image, the value of each being a URL to an image with my firebase storage.
What I'd like to do is pull the image directly from storage as part of the loop that pulls from firestore.
I am trying to do this as when this loop renders the items, the images are taking a few seconds to appear on the screen.
Please advise if there is an alternative to solve this issue?
FirebaseData.js
onCollectionUpdate = (querySnapshot) => {
const data = [];
querySnapshot.forEach((doc) => {
const { title, image, url, description } = doc.data();
data.push({
key: doc.id,
doc, // DocumentSnapshot
title,
image,
description,
url
});
});
this.setState({
data,
loading: false
});
}
render() {
if (this.state.loading) {
return <Spinner />; //RETURN A LOADING ICON
}
return (
<List containerStyle={styles.listStyle}>
<FlatList
data={this.state.data}
renderItem={({ item }) => <ChildList {...item} />}
/>
</List>
);
}
}
ChildList.js
export default class ChildListRow2 extends React.PureComponent {
render() {
return (
<ListItem
title={
<View>
<Image source={{ uri: this.props.image }} style={styles.imageStyle} />
<Text style={styles.titleTextStyle}>{this.props.title}</Text>
</View>
}
subtitle={
<View>
<Text style={styles.subTitleTextStyle}>{this.props.description}</Text>
</View>
}
hideChevron
onPress={() => Linking.openURL(this.props.url)}
containerStyle={styles.listItemStyle}
/>
);
}
}

You can use onLoadEnd and onLoadStart properties of Image component to show and hide the rest of the data. You can show a loading spinner till the file loads and then show the complete component when it finishes.
Example
export default class ChildListRow2 extends React.PureComponent {
constructor() {
super();
this.state = {
loaded: false
};
}
imageLoaded = () => {
this.setState({ loaded: true })
}
render() {
return (
<ListItem
title={
<View>
<Image
source={{ uri: this.props.image }}
style={styles.imageStyle}
onLoadEnd={this.imageLoaded}
/>
<Text style={styles.titleTextStyle}>{this.props.title}</Text>
</View>
}
subtitle={
<View>
<Text style={styles.subTitleTextStyle}>{this.props.description}</Text>
</View>
}
hideChevron={this.state.loaded}
onPress={() => Linking.openURL(this.props.url)}
containerStyle={styles.listItemStyle}
/>
);
}
}

Related

Calling function of class based component from another component in react native

I have two components one is Messages and other component is Navigation it's a stack navigator component. I would like to call function named onRefresh of Messages component from component Navigation header buttons. Please see my code how can I achieve this.
Messages.js (component file)
export default class Messages extends Component {
// Constructor
constructor(props) {
super(props)
this.state = {
messages: [],
isLoading: true
};
}
// End Constructor
// Getting Messages from server
async getMessages() {
try {
const response = await fetch('https://reactnative.dev/movies.json');
const json = await response.json();
this.setState({ messages: json.movies });
} catch (error) {
console.log(error);
} finally {
this.setState({ isLoading: false });
}
}
// End Getting messages from server
componentDidMount() {
this.getMessages();
}
// On refresh the messages
onRefresh = async () => {
this.setState({isLoading: true,}, () => {this.getMessages();});
}
// Send now sms using button.
sendNowMessage = async (title) => {
Alert.alert(title, "asdasdas");
}
render() {
const { messages, isLoading } = this.state;
return (
<SafeAreaView style={styles.container}>
{isLoading ? <ActivityIndicator size="large" color="#0000ff" style={ styles.horizontal } /> : (
<FlatList
data={ messages }
keyExtractor={({ id }, index) => id}
onRefresh={() => this.onRefresh()}
refreshing={this.state.isLoading}
renderItem={({ item }) => (
<View style={styles.item}>
<Text style={{ marginBottom: 12, }}>{item.title}, {item.releaseYear}</Text>
<Button
onPress={() => this.sendNowMessage(item.title)}
title="Send SMS"
style={styles.sendSMS}
color="#841584"
accessibilityLabel="Learn more about this purple button"
/>
</View>
)}
/>
)}
</SafeAreaView>
);
}
}
Navigation.js (component file)
const Stack = createStackNavigator();
export default function Navigation() {
return (
<Stack.Navigator>
<Stack.Screen
name="AuthScreen"
component={AuthScreen}
options={{
title: "Welcome",
}}
/>
<Stack.Screen
name="Messages"
component={Messages}
options={{
headerTitle: 'Messages',
headerRight: () => {
return (
<View style={styles.headerButtons}>
<View style={{ marginRight:10, }}>
<TouchableOpacity
onPress={() => {
new Messages().onRefresh() <---- I am calling it like this.
}}
>
<Text
style={styles.actionButtons}
>
<Ionicons name="reload-sharp" size={20} color="black" />
</Text>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity
onPress={() => {
alert("working")
}}
>
<Text
style={styles.actionButtons}
>
<Ionicons name="filter" size={20} color="black" />
</Text>
</TouchableOpacity>
</View>
</View>
)
}
}}
/>
</Stack.Navigator>
);
}
I am calling onRefresh function like this new Messages().onRefresh() and getting the following error.
Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the Messages component.
I want to learn what is the best way to call onRefresh function.

Impossible to display the data after a get request react native

i'm trying to display information of a restaurant in my react-native app after i get it by a get request from my database. I don't have problem getting the data, it's the display the problem.
Inside my component RestaurantDetailScreen :
componentDidMount() {
this.getRestaurantInfo()
}
getRestaurantInfo = async () => {
try {
let res = await axios({
method: 'get',
url: `${environment.apiDevUrl}/restaurant/${this.state.restaurantId}`,
})
this.setState({
restaurantInfo: res.data,
})
console.log(this.state.restaurantInfo.restaurant.nom);
} catch (error) {
console.error(error)
}
}
The console log display my state with correct data from my backend, but when I try to render it inside my Flatlist there is nothing to display:
<ScrollView style={styles.container}>
{/* <Text>{this.state.restaurantInfo.restaurant.nom}</Text> */}
<FlatList
data={this.state.restaurantInfo}
renderItem={this.renderRestaurantInfo}
keyExtractor={item => `${item.restaurant.id}`}
/>
<SectionList
sections={productData}
ItemSeparatorComponent={this.renderSeparator}
renderItem={this.renderRestaurantDetail}
renderSectionHeader={({section: {header}}) => (
<Text style={styles.sectionHeader}>{header}</Text>
)}
keyExtractor={(item, index) => `${item.id}` + index}
ref={ref => (this.sectionListRef = ref)}
getItemLayout={this.getItemLayout}
/>
</ScrollView>
So i've tried to simply display the name of a restaurant inside a Text component like in the getRestaurantInfo() method but it will return a undefined if i don't remove it.
The render method renderRestaurantInfo() :
renderRestaurantInfo = ({item}) => (
<View style={styles.containerRestaurantInfo}>
{console.log('item : ', item)}
<ItemRestaurantInfo
// image={item.restaurant.image}
title={item.restaurant.nom}
description={item.restaurant.description}
categories={item.restaurant.categorie}
adress={item.restaurant.adresse.rue1}
scheduleBeginning={item.restaurant.horaires.crenaux.hDebut}
scheduleEnd={item.restaurant.horaires.crenaux.hFin}
/>
</View>
)
And finally the ItemRestaurantInfo component :
ItemRestaurantInfo = ({
image,
title,
categories,
adress,
description,
scheduleBeginning,
scheduleEnd,
}) => {
return (
<View>
{/* <Image source={image} style={styles.topImage} /> */}
<View style={{padding: 15}}>
<Text style={styles.restaurantTitle}>{title}</Text>
<Text style={styles.restaurantInfoText}>{`${categories}`}</Text>
<View style={styles.viewInRow}>
<Image
style={{margin: 5}}
source={require('../../assets/icons/map-pin.png')}
/>
<Text style={styles.restaurantInfoText}>{adress}</Text>
</View>
<Text style={styles.restaurantInfoText}>{description}</Text>
<View style={styles.viewInRow}>
<Image
style={{margin: 5}}
source={require('../../assets/icons/calendar.png')}
/>
<Text style={styles.restaurantInfoText}>{scheduleBeginning} - {scheduleEnd}</Text>
</View>
</View>
</View>
)
}
You forget to pass item in your FlatList renderItem:
<FlatList
data={this.state.restaurantInfo}
renderItem={({item}) => this.renderRestaurantInfo(item)}
...
/>
You could also return a Component instead of method:
const RenderItems = ({ title }) => {
return (
<View>
<Text>{title}</Text>
</View>
);
}
...
// Pass the component to your FlatList
<FlatList
data={YOURDATA}
renderItem={({ item }) => <RenderItems title={item.title} />}
...
/>
And same thing goes for your SectionList
Edit: Working condeSandbox example with data fetch.
Ok I have finally find the solution, it was because my array restaurantInfo in the state was transform in an object when I use setState in the call of my backend... so inside the render method of the flatList there was no data.
OLD CODE
getRestaurantInfo = async () => {
try {
let res = await axios({
method: 'get',
url: `${environment.apiDevUrl}/restaurant/${this.state.restaurantId}`,
})
this.setState({
restaurantInfo: res.data,
})
} catch (error) {
console.error(error)
}
}
NEW CODE
getRestaurantInfo = async () => {
try {
let res = await axios({
method: 'get',
url: `${environment.apiDevUrl}/restaurant/${this.state.restaurantId}`,
})
let newArray = [...this.state.restaurantInfo];
newArray[0] = res.data
this.setState({
restaurantInfo: newArray,
})
console.log('state dans getResturantinfo:',this.state.restaurantInfo);
} catch (error) {
console.error(error)
}
}
With this syntax it force react to keep an array. And finally it was not necessery to use this syntax : renderItem={({item}) => this.renderRestaurantInfo(item)}
In my code I keep it like this :
<FlatList
data={this.state.restaurantInfo}
renderItem={this.renderRestaurantInfo}
keyExtractor={item => `${item.restaurant.id}`}
/>

Access state in container component

How can I access (get and set) state inside a container component?
I am getting Undefined is not an object evaluating this.state.* because it cannot be accessed from MultiImagePost. Is there a way to bind this to the container component?
export default class ImagePost extends React.Component {
state = {
index: 0,
modalVisible: false,
currentImages: []
};
render() {
const { imageCount } = this.props;
if (imageCount == 1) {
return <SingleImagePost postObject={this.props} />
} else {
return <MultiImagePost postObject={this.props} />
}
}
const MultiImagePost = ({ postObject }) => (
<View>
<FlatList
data={[{key: 'a'}]}
renderItem={({item}) =>
<TouchableOpacity>
<View>
<FlatGrid
itemDimension={100}
items={postObject.images}
renderItem={({ item, index }) => (
<TouchableHighlight
onPress={() => {
this.setState({
modalVisible: true,
index: this.state.index,
currentImages: postObject.images
});
}}>
<Image
source={{ uri: item }}
/>
</TouchableHighlight>
)}
/>
</View>
</TouchableOpacity>
}
/>
</View>
);
}
MultiImagePost is a stateless(functional) component, so it has no state.
U should manage state in ImagePost or consider using React hook.
I can't run your code, if MultiImagePost is inside ImagePost, you may try to see if it works.
I think you have mixed up class component with functional component. You need to change your code a bit like,
export default class ImagePost extends React.Component {
state = {
index: 0,
modalVisible: false,
currentImages: []
};
render() {
const { imageCount } = this.props;
if (imageCount == 1) {
return <SingleImagePost postObject={this.props} />
} else {
return <>{this.MultiImagePost(this.props)}</> //Instead of component make it as function call
}
}
//Make this as instance function
MultiImagePost = (postObject) => (
<View>
<FlatList
data={[{key: 'a'}]}
renderItem={({item}) =>
<TouchableOpacity>
<View>
<FlatGrid
itemDimension={100}
items={postObject.images}
renderItem={({ item, index }) => (
<TouchableHighlight
onPress={() => {
this.setState({
modalVisible: true,
index: this.state.index,
currentImages: postObject.images
});
}}>
<Image source={{ uri: item }} />
</TouchableHighlight>
)}
/>
</View>
</TouchableOpacity>
}
/>
</View>
);
}

How to setState more than one object and pass to other component?

I'm new at react-native. I try a lot of ways to get arrays of JSON data from openweathermap's API but didn't work. I want some suggestions and guidelines.
I just fetch JSON data and want to set state more than one object from JSON data.
Example of JSON data. I want to get temp, humidity, description and icon from 2 arrays in list[].
"list":[
{
"dt":1541440800,
"main":{
"temp":25.85,
"temp_min":25.85,
"temp_max":27.35,
"pressure":1022.17,
"sea_level":1023.04,
"grnd_level":1022.17,
"humidity":100,
"temp_kf":-1.5
},
"weather":[
{
"id":500,
"main":"Rain",
"description":"light rain",
"icon":"10n"
}
],
"dt_txt":"2018-11-05 18:00:00"
},
{
"dt":1541451600,
"main":{
"temp":26.38,
"temp_min":26.38,
"temp_max":27.5,
"pressure":1021.34,
"sea_level":1022.24,
"grnd_level":1021.34,
"humidity":100,
"temp_kf":-1.12
},
"weather":[
{
"id":802,
"main":"Clouds",
"description":"scattered clouds",
"icon":"03n"
}
],
"dt_txt":"2018-11-05 21:00:00"
}
Here is my JSON fetching component. I want to show different data from arrays by passing state to front-end component.
export default class Forecast extends Component {
constructor(props) {
super(props);
this.state = {
hourlyForecast: {}
};
}
fetchData = () => {
fetch(
`http://api.openweathermap.org/data/2.5/forecast?q=${
this.props.currentCity
}&units=metric&appid=${API_KEY}`
)
.then(response => response.json())
.then(json => {
this.setState({
hourlyForecast: {
temp: json.list[0].main.temp,
humidity: json.list[0].main.humidity,
icon: json.list[0].weather[0].icon,
description: json.list[0].weather[0].description,
date_time: json.list[0].dt_txt
}
});
})
.catch(error => {
console.warn(error);
});
};
componentDidMount = () => this.fetchData();
render() {
return (
<Container>
<Content>
<ForecastCards {...this.state.hourlyForecast} />
<ForecastCards {...this.state.hourlyForecast} />
</Content>
</Container>
);
}
}
And here is my front-end component.
export default class ForecastCards extends Component {
render() {
return (
<Card>
<CardItem>
<Body>
<View style={{flex: 1, flexDirection: "row"}}>
<View>
<Text>{this.props.date_time}</Text>
<Text>Temperature: {this.props.temp} °C</Text>
<Text>Humidity: {this.props.humidity} %</Text>
</View>
<View style={{marginLeft: 100}}>
<Image
style={{ width: 60, height: 60 }}
source={{
uri: `http://openweathermap.org/img/w/${
this.props.icon
}.png`
}}
/>
<Text>{this.props.description}</Text>
</View>
</View>
</Body>
</CardItem>
</Card>
);
}
}
Here you go with a solution
fetchData = () => {
fetch(
`http://api.openweathermap.org/data/2.5/forecast?q=${this.props.currentCity}&units=metric&appid=${API_KEY}`
)
.then(response => response.json())
.then(json => {
this.setState({
hourlyForecast: {
temp: json.list[0].main.temp,
humidity: json.list[0].main.humidity,
icon: json.list[0].weather[0].icon,
description: json.list[0].weather[0].description,
date_time: json.list[0].dt_txt
}
});
})
.catch(error => {
console.warn(error);
});
};
componentDidMount = () => this.fetchData();
render() {
return (
<Container>
<Content>
<ForecastCards hourlyForecast={this.state.hourlyForecast} />
<ForecastCards hourlyForecast={this.state.hourlyForecast} />
</Content>
</Container>
);
}
And here is my front-end component.
export default class ForecastCards extends Component {
render() {
return (
<Card>
<CardItem>
<Body>
<View style={{flex: 1, flexDirection: "row"}}>
<View>
<Text>{this.props.hourlyForecast.date_time}</Text>
<Text>Temperature: {this.props.hourlyForecast.temp} °C</Text>
<Text>Humidity: {this.props.hourlyForecast.humidity} %</Text>
</View>
<View style={{marginLeft: 100}}>
<Image
style={{ width: 60, height: 60 }}
source={{
uri: `http://openweathermap.org/img/w/${
this.props.hourlyForecast.icon
}.png`
}}
/>
<Text>{this.props.hourlyForecast.description}</Text>
</View>
</View>
</Body>
</CardItem>
</Card>
);
}
}
ForecastCards.propTypes = {
hourlyForecast: PropTypes.object
}
Setting the state is fine. For passing the props <ForecastCards hourlyForecast={this.state.hourlyForecast} />
Provide the PropType in ForecastCards component using ForecastCards.propTypes = {
hourlyForecast: PropTypes.object
}
For access date_time or any other key, please use this.props.hourlyForecast.date_time or this.props.hourlyForecast.{key}
Hope this will help you.

How to scroll a page in React Native

I can't scroll my page. What is wrong?
export default class CustomersTab extends Component {
constructor(props) {
super(props);
this.state = {
token: "",
isLoading: true,
dataSource: null
};
}
componentWillMount() { MY APİ CODES - HERE İS NOT İMPORTANT
)
.then(response => response.json())
.then(responseData => {
this.setState({
isLoading: false,
dataSource: responseData.result
});
});
})
.catch(error => console.warn(error));
}
static navigationOptions = {
title: "Firmaların Listesi",
tabBarIcon: <Icon name="ios-people" style={{}} />
};
render() {
if (this.state.isLoading) {
return (
<View style={styles.container}>
<ActivityIndicator />
</View>
);
} else {
return this.state.dataSource.map((val, key) => {
return (
<List key={key}>
<ListItem
roundAvatar
avatar={{ uri: "url/notimportant" }}
key={key}
title={val.name+" "+val.surname}
subtitle={val.customerType}
/>
</List>
);
});
}
}
}
I tried different codes but it did not improve. How can I view all the data from the page? The "Load others" button may also work. Does anyone have an idea??
Note: DATA is ok. The problem is scroll.
Your render elements should be wrapped withing ScrollView.
return (
<ScrollView>
{this.state.dataSource.map((val, key) => {
return (
<List key={key}>
<ListItem
roundAvatar
avatar={{ uri: "url/notimportant" }}
key={key}
title={val.name + " " + val.surname}
subtitle={val.customerType}
/>
</List>
);
})}
</ScrollView>
);
you can use FlatList and ScrollView. remember to import them. if you see the react-native docs and read it you can use it. another tag that you use it like View don't scroll.

Resources