Image is not displaying after loading from database? - reactjs

I'm quite new to react native. I have a user with a profile picture that I'm trying to display on screen, when printing to my console snapshot.val().imageURL the correct url is displayed using this:
var profImage = 'https://www.placeholdit.com';
var user = authFB.currentUser;
if (user) {
database.ref('users').child(user.uid).once('value')
.then((snapshot) => profImage = snapshot.val().imageURL)
.catch(error => console.log(error))
} else {
console.log("No user")
}
I assign that url to profImage and try to set my image uri to that variable:
<Avatar
large
rounded
source={{ uri: profImage }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
However, my image container remains blank. Does this have to do that when the render function is run, the url hasn't been retrieved thus the component display is blank? Do I use this.setState to update the component? If so, what is the proper way to do so?
Here is the relevant part of my component:
class Profile extends React.Component {
static navigationOptions = {
title: "Profile",
headerStyle: {
backgroundColor: '#fff',
borderBottomWidth: 0,
},
headerTitleStyle: {
color: 'black'
},
}
constructor() {
super();
this.state = {
image: null,
uploading: null,
}
}
render() {
const { navigate } = this.props.navigation;
let { image } = this.state;
var profImage = 'https://www.placeholdit.com';
var user = authFB.currentUser;
if (user) {
database.ref('users').child(user.uid).once('value')
.then((snapshot) => profImage = snapshot.val().imageURL)
.catch(error => console.log(error))
} else {
console.log("No user")
}
return (
<ScrollView>
<View style={styles.profileTopContainer}>
<Avatar
large
rounded
source={{ uri: img }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
<Text
style={styles.usernameText}
>
Jordan Lewallen
</Text>
<TouchableHighlight
onPress={this._pickImage}>
<Text
style={styles.userMinutes}
>
Choose a profile picture
</Text>
</TouchableHighlight>
{this._maybeRenderImage()}
{this._maybeRenderUploadingOverlay()}
{
(image)
? <Image source={{ uri: image }} style={{ width: 250, height: 250 }} />
: null
}
</View>
}

please try this
<Avatar
large
rounded
source={{ uri: img }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
replace with
<Avatar
large
rounded
source={{ uri: profImage }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>

This is because you call the async request inside your render method itself.
When your render method execute you request for imageUrl which is a async call and takes time to resolve and update the value of profImage variable. At that time your render method is finished its execution and set the placeholder.
If you need this to be done you should keep this profImage in your component state. So once the state is updated your render method will be called again and update the UI with new image.
Try this!
class Profile extends React.Component {
static navigationOptions = {
title: "Profile",
headerStyle: {
backgroundColor: '#fff',
borderBottomWidth: 0,
},
headerTitleStyle: {
color: 'black'
},
}
constructor() {
super();
this.state = {
image: null,
uploading: null,
}
}
componentWillRecieveProps(nextProps){
var profImage = 'https://www.placeholdit.com';
var user = authFB.currentUser;
if (user) {
database.ref('users').child(user.uid).once('value')
.then((snapshot) => this.setState({ image: snapshot.val().imageURL }))
.catch(error => console.log(error))
} else {
this.setState({ image: 'https://www.placeholdit.com' });
console.log("No user")
}
}
render() {
const { navigate } = this.props.navigation;
let { image } = this.state;
return (
<ScrollView>
<View style={styles.profileTopContainer}>
<Avatar
large
rounded
source={{ uri: image }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
<Text
style={styles.usernameText}
>
Jordan Lewallen
</Text>
<TouchableHighlight
onPress={this._pickImage}>
<Text
style={styles.userMinutes}
>
Choose a profile picture
</Text>
</TouchableHighlight>
{this._maybeRenderImage()}
{this._maybeRenderUploadingOverlay()}
{
(image)
? <Image source={{ uri: image }} style={{ width: 250, height: 250 }} />
: null
}
</View>
}

Related

React Native Update Parent Array from Child Component

I am having trouble updating an array that is passed as a prop into my child component. I have searched around but haven't found an answer that can directly solve my problem. My code is as follows:
App.js
import React, { Component } from 'react';
import { StyleSheet, Text, View, SafeAreaView } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import AddMedication from "./src/AddMedication";
import MedicationList from './src/MedicationList';
const Stack = createNativeStackNavigator();
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
medications: [],
}
this.addMedication = this.addMedication.bind(this);
}
addMedication = (name, dosage, measurement, timesDaily) => {
console.log("Medication added.")
var newItem = {name: name, dosage: dosage, measurement: measurement, timesDaily: timesDaily}
this.setState({
medications: [...this.state.medications, newItem]
})
}
render() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Medication List">
{(props) => <MedicationList {...props} medications={this.state.medications} />}
</Stack.Screen>
<Stack.Screen name="Add New Medication">
{(props) => <AddMedication {...props} addMedication={this.addMedication} />}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
);
}
}
This is the home screen where I am trying to display the array but nothing shows up
MedicationList.js
class MedicationList extends Component {
constructor(props) {
super(props);
this.state = {
tableHead: ['Name', 'Dosage', 'Times Daily', 'Prescriber', 'For Diagnosis'],
}
}
medication = ({ item }) => {
<View style={{ flexDirection: 'row' }}>
<View style={{ width: 50, backgroundColor: 'lightyellow'}}>
<Text style={{ fontSize: 16, fontWeight: 'bold', textAlign: 'center'}}>{item.name}</Text>
</View>
<View style={{ width: 400, backgroundColor: 'lightpink'}}>
<Text style={{ fontSize: 16, fontWeight: 'bold' , textAlign: 'center'}}>{item.dosage}{item.selectedMeasurement}</Text>
</View>
<View style={{ width: 400, backgroundColor: 'lavender'}}>
<Text style={{ fontSize: 16, fontWeight: 'bold' , textAlign: 'center'}}>{item.timesDaiy}</Text>
</View>
</View>
}
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', marginTop: '10%'}}>
<Button
title="+ Add New Medication"
onPress={() => {
/* 1. Navigate to the Details route with params */
this.props.navigation.navigate('Add New Medication', {
medications: this.props.medications,
});
}}
/>
<FlatList
data={this.props.medications}
renderItem={this.medication}
/>
</View>
);
}
}
This is where I click the add button to update the medications array
AddMedication.js
class AddMedication extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
dosage: 0,
selectedMeasurement: "mg",
timesDaily: '',
prescriber: '',
forDiagnoses: '',
instructions: '',
validity: false,
};
}
setName = (name) => {
let isValid = this.isFormValid();
this.setState({ name: name, validity: isValid });
}
setDosage = (dosage) => {
let isValid = this.isFormValid();
this.setState({ dosage: dosage, validity: isValid });
}
setMeasurement = (measurement) => {
this.setState({ selectedMeasurement: measurement });
}
setTimesDaily = (timesDaily) => {
let isValid = this.isFormValid();
this.setState({ timesDaily: timesDaily, validity: isValid });
}
setPrescriber = (prescriber) => {
this.setState({ prescriber: prescriber });
}
setDiagnoses = (diagnoses) => {
this.setState({ forDiagnoses: diagnoses });
}
setInstructions = (instructions) => {
this.setState({ instructions: instructions });
}
isFormValid = () => {
return (this.state.name !== '' && (this.state.dosage !== '' && this.state.dosage > 0)
&& (this.state.timesDaily !== '' && this.state.timesDaily > 0));
}
render() {
return (
<View style={styles.container}>
<Text style={{color: 'red', marginBottom: 5, marginLeft: -125}}>* denotes required field</Text>
<View style={{flexDirection: 'row'}}>
<Text style={styles.required}>*</Text>
<TextInput
style={styles.inputText}
onChangeText={(name) => this.setName(name)}
placeholder="Medication Name"
value={this.state.name}
/>
</View>
<View style={{flexDirection: 'row'}}>
<Text style={styles.required}>*</Text>
<TextInput
style={styles.inputText}
onChangeText={(dosage) => this.setDosage(dosage)}
placeholder="Dosage"
value={this.state.dosage}
/>
</View>
<View style={styles.dosageContainer}>
<Text style={{flex: 1, marginTop: 100, marginLeft: 30}}>
Select Measurement:
</Text>
<Picker
style={styles.picker}
selectedValue={this.state.selectedMeasurement}
onValueChange={(itemValue, itemIndex) =>
this.setMeasurement(itemValue)
}>
<Picker.Item label="mg" value="mg" />
<Picker.Item label="g" value="g" />
<Picker.Item label="ml" value="ml" />
</Picker>
</View>
<View style={{flexDirection: 'row'}}>
<Text style={styles.required}>*</Text>
<TextInput
style={styles.inputText}
onChangeText={(timesDaily) => this.setTimesDaily(timesDaily)}
placeholder="Times daily"
value={this.state.timesDaily}
/>
</View>
<TextInput
style={styles.inputText}
onChangeText={(prescriber) => this.setPrescriber(prescriber)}
placeholder="Prescriber"
value={this.state.prescriber}
/>
<TextInput
style={styles.inputText}
onChangeText={(diagnoses) => this.setDiagnoses(diagnoses)}
placeholder="For diagnoses"
value={this.state.forDiagnoses}
/>
<TextInput
style={styles.inputText}
onChangeText={(instructions) => this.setInstructions(instructions)}
placeholder="Instructions"
value={this.state.instructions}
/>
<TouchableOpacity
style={this.isFormValid() ? styles.validButton : styles.invalidButton}
disabled={!Boolean(this.state.name && this.state.dosage && this.state.timesDaily)}
onPress={() => {
this.props.navigation.goBack()
this.props.addMedication(this.state.name, this.state.dosage,
this.state.selectedMeasurement, this.state.timesDaily)
}}
>
<Text style={{color: 'white'}}>Add Medication</Text>
</TouchableOpacity>
</View>
)
}
}
You can pass the state value but I think you cannot pass the addMedication method just like this.
Could you please try passing an arrow function that uses the setState method?
For example:
<Stack.Screen name="Add New Medication">
{(props) => <AddMedication {...props} addMedication={(name, dosage, measurement, timesDaily)=> {this.addMedication(name, dosage, measurement, timesDaily)}} />}
</Stack.Screen>

React-native FlatList rendering issue

THE PROBLEM
see the screenshot of probleme
//below code is which loades the slider..you can also find the below snippet in ClassFlatListRender.js , when id of element passed through FlatList which equals to the selected Id then the audio slider should work, but if I trigger one audio file element all of the sliders are moving
{id == this.state.selectedOptionId ?
(<View style={[styles.viewBar, { flexDirection: 'row', }]}>
<View style={[styles.viewBarPlay, { width: playWidthMessage }]} />
</View)
:
<View><Text>no slider<Text></View>}
THE CODE
The structure of my code is this: I have a container component with all the logic and state,
which contains a FlatList component , which again contains a custom presentational List.
Container
Custom list component that includes the FlatList component and the renderItem method
invokes classFlatList component (presentational, stateless)
The container includes this component
<CustomList
items={this.state.data}
/>
CustomList:
export class CustomList extends Component {
render() {
const renderItem = ({ item }) => {
return (
<ClassFlatListRender
message={item.message}
id={item.id}
user={item.user}
timestamp={item.timestamp}
heights={item.heights}
url={item.url}
audioLength={item.audioLength}
type={item.type}
/>
)
}
let { items } = this.props
return (
<View style={styles.container}>
< FlatList
style={styles.container}
data={items.slice().sort((a, b) => b.timestamp - a.timestamp)}
// data={data}
renderItem={renderItem}
inverted={true}
extraData={items}
keyExtractor={(items) => `${items.timestamp}`}
/>
</View>
)
}
}
The ClassFlatListRender.js
export class ClassFlatListRender extends React.Component {
constructor() {
super();
this.state = {
update: false,
currentPositionSec: 0,
currentDurationSec: 0,
playTime: '00:00:00',
duration: '00:00:00',
id: '',
tempId: '',
selectedOptionId: '',
setId: '',
};
this.audioRecorderPlayer = new AudioRecorderPlayer();
this.audioRecorderPlayer.setSubscriptionDuration(0.09); // optional. Default is 0.1
}
onStartPlay = async (id, url) => {
console.log('onStartPlay');
//? Custom path
// const msg = await this.audioRecorderPlayer.startPlayer(this.path);
//? Default path
console.log("arvinf", url)
const msg = await this.audioRecorderPlayer.startPlayer(url);
console.log("msg", msg)
const volume = await this.audioRecorderPlayer.setVolume(1.0);
console.log(`file: ${msg}`, `volume: ${volume}`);
this.audioRecorderPlayer.addPlayBackListener((e) => {
this.setState({
currentPositionSec: e.currentPosition,
currentDurationSec: e.duration,
// playWidth: (e.currentPosition / e.duration) * (screenWidth - 300)
// playTime: this.audioRecorderPlayer.mmssss(
// Math.floor(e.currentPosition),
// ),
// duration: this.audioRecorderPlayer.mmssss(Math.floor(e.duration)),
});
});
};
onPausePlay = async () => {
await this.audioRecorderPlayer.pausePlayer();
};
renderElement = (id, audioLength) => {
if (this.state.selectedOptionId == id) {
// this.onStartPlay(url)
return (
<Fcon name={this.state.selectedOptionId == id ? 'play' : 'pasue'} size={24} color={'white'} />
)
}
if (this.state.selectedOptionId !== id || this.state.currentPositionSec == this.state.duration) {
// this.onPausePlay(url)
return (
<Fcon name={'play'} size={24} color={'white'} />
)
}
}
checkFunction = async (id, url) => {
console.log("id: before", id)
await this.setState({
selectedOptionId: id,
setId: id
})
console.log('id:after', this.state.selectedOptionId)
id == this.state.setId ? this.onStartPlay(id, url) : this.onPausePlay()
// return this.state.selectedOptionId
}
render() {
let { id, user, url, heights, message, audioLength, type } = this.props;
let playWidthMessage =
(this.state.currentPositionSec / this.state.currentDurationSec) *
(screenWidth - 300);
if (!playWidthMessage) {
playWidthMessage = 0;
}
return (
<View id={id}>
{
url.endsWith(".jpg") ?
< View key={id} style={[user == 1 ? styles.receiver : styles.sender, { paddingHorizontal: 14, paddingVertical: 8, borderRadius: heights > 50 ? 20 : 50 }]}>
<View style={{ alignItems: 'center', }}>
{console.log("address", url)}
<Image
resizeMode="contain"
// resizeMethod="scale"
style={{ width: 200, height: 200 }}
source={{ uri: url, width: 200, height: 200 }}
// source={{ uri: "https://picsum.photos/200", width: 200, height: 200 }}
// source={require("https://ibb.co/hM8BbY5")}
/>
</View>
</View> : null
}
{
url.endsWith(".mp3") ?
(
<View key={this.props.id} style={[user == 1 ? styles.receiver : styles.sender, { paddingHorizontal: 14, paddingVertical: 8, borderRadius: heights > 50 ? 20 : 50 }]}>
<View style={[styles.viewPlayer, { backgroundColor: user == 1 ? darkgrey : '#0096FF', alignSelf: 'center', borderRadius: 50, flexDirection: 'row', justifyContent: "center", alignItems: 'center' }]}>
{/* line */}
<TouchableOpacity activeOpacity={0.5} onPress={() => {
this.checkFunction(id, url)
}} style={{ borderRadius: 50, width: 36, height: 36, justifyContent: 'center', alignItems: 'center' }}>
<View>
{this.renderElement(id, url, audioLength)}
</View>
</TouchableOpacity>
<View >
<TouchableOpacity
style={[styles.viewBarWrapper, { width: screenWidth - 300 }]}
onPress={this.onStatusPress}
>
{id == this.state.setId ?
(<View style={[styles.viewBar, { flexDirection: 'row', }]}>
{/* {console.log("playWidth:", this.state.playWidth)} */}
<View style={[styles.viewBarPlay, { width: playWidthMessage }]} />
</View>
)
: <View><Text>ddd</Text></View>}
</TouchableOpacity>
</View>
<Text style={[styles.txtRecordCounter, { color: 'white' }]}>{audioLength}</Text>
</View>
</View >) : null
}
{
type == "txt" ?
// && user == 1 ?
<View key={id} >
<TouchableOpacity onPress={async () => await this.setState({ tempId: id })} style={[styles.receiver, { borderRadius: heights > 50 ? 20 : 50, backgroundColor: this.state.tempId == id ? 'pink' : 'yellow' }]}>
<Text style={styles.receiverText}>{message}</Text>
</TouchableOpacity>
</View> : null
}
</View >
)
}
}
Maintain the selected audio id in your parent component, pass it to child components using props, animate based on the condition. in ur message component

API image not being displayed in list

I know there are several questions with this issue but mine is different.
I trying to display an image in my Flatlist card that is coming from an API. However it is not showing up.
BUT...when I display this image in another part of my code (in an Autocomplete list) using the same code basically, it works. Also, when I try an url from an image on the Web, it displays inside the flatlist correctly
Here's my Flatlist code:
<FlatList
data={this.state.myGamesArray}
renderItem={({ item }) => (
<Card>
<CardItem>
<View>
<Image
style={styles.gameImage}
source={{uri: item.background_image}}
/>
</View>
</CardItem>
<CardItem>
<View>
<Text style={styles.usergameText}>
{item}
</Text>
</View>
</CardItem>
</Card>
)}
keyExtractor={(item,index) => index.toString()}
/>
Here is my Autocomplete code in which I use the same image bracket-thingy
<View style={styles.iconContainer} >
<TouchableOpacity onPress={() => this.setState({ query: item.name})}
style={styles.autocompleteList} >
<View>
<Image
style={styles.gameImage}
source={{uri: item.background_image}}
/>
</View>
<Text style={styles.gameText}>
{item.name}
</Text>
</TouchableOpacity>
</View>
I ran console.item(item.background_image) both inside the Flatlist(first snippet) and the Autocomplete list (Second snippet). The first shows 'undefined' and the second it shows all the URIs
App.js full code:
/*This is an example of AutoComplete Input/ AutoSuggestion Input*/
import React, { Component } from 'react';
//import react in our code.
import { StyleSheet, Text, TouchableOpacity, View, Image, FlatList, Alert, TouchableWithoutFeedback, Keyboard } from 'react-native';
//import all the components we are going to use.
import Autocomplete from 'react-native-autocomplete-input';
import { Button, List, Container, ListItem, Card, CardItem, Header, Item } from 'native-base';
import { Entypo } from '#expo/vector-icons'
//import Autocomplete component
//const API = 'https://api.rawg.io/api/games?page=1';
//Demo base API to get the data for the Autocomplete suggestion
class App extends Component {
constructor(props) {
super(props);
//Initialization of state
//films will contain the array of suggestion
//query will have the input from the autocomplete input
this.state = {
myGamesArray: [],
games: [],
query: ' ',
};
}
componentDidMount() {
//First method to be called after components mount
//fetch the data from the server for the suggestion
fetch('https://api.rawg.io/api/games?page=1&platforms=18', {
"method": "GET",
"headers": {
"x-rapidapi-host": "rawg-video-games-database.p.rapidapi.com",
"x-rapidapi-key": "495a18eab9msh50938d62f12fc40p1a3b83jsnac8ffeb4469f"
}
})
.then(res => res.json())
.then(json => {
const { results: games } = json;
this.setState({ games });
//setting the data in the games state
});
}
findGame(query) {
let i;
//method called everytime when we change the value of the input
if (query === '') {
//if the query is null then return blank
return [];
}
const { games } = this.state;
//making a case insensitive regular expression to get similar value from the film json
const regex = new RegExp(`${query.trim()}`, 'i');
//return the filtered game array according the query from the input
return games.filter(game => game.name.search(regex) >= 0);
}
AddItemsToArray = () => {
var i
//verifica se input esta vazio
if (this.state.query === '') {
return Alert.alert('Voce não selecionou um jogo')
}
//VERIFY IF GAME IS IN THE ARRAY
for (i = 0; i < this.state.games.length - 1; i++) {
if (this.state.query !== this.state.games[i].name) {
if (i === this.state.games.length - 2) {
return Alert.alert('Este jogo nao existe')
}
else {
continue
}
} else {
break
}
}
//verifica repetido
if (this.state.myGamesArray.includes(this.state.query)) {
return Alert.alert('Este jogo já foi adicionado')
}
else {
//Adding Items To Array.
this.setState(prevState => {
const { myGamesArray, query } = prevState;
return {
myGamesArray: [...myGamesArray, query.toString()],
};
},
// Use setState callback to alert with the updated state
);
}
}
render() {
const { query } = this.state;
const games = this.findGame(query);
const comp = (a, b) => a.toLowerCase().trim() === b.toLowerCase().trim();
return (
<TouchableWithoutFeedback
onPress={() => {
Keyboard.dismiss()
}}
>
<View style={styles.container}>
<View style={styles.listContainer}
>
<FlatList
data={this.state.myGamesArray}
renderItem={({ item }) => (
console.log(item.background_image),
<Card style={{flexDirection:'row',paddingEnd:100}}>
<CardItem cardBody>
<View>
<Image
style={styles.listImage}
source={{uri: item.background_image}}
/>
</View>
</CardItem>
<CardItem>
<View>
<Text style={styles.usergameText}>
{item}
</Text>
<Text style={styles.usergameText}>
Playstation 4
</Text>
</View>
</CardItem>
</Card>
)}
keyExtractor={(item,index) => index.toString()}
/>
</View>
<View>
<Header span
style={styles.header}>
<Autocomplete
inputContainerStyle={{borderColor:'transparent'}}
style={styles.autocompleteInput}
autoCapitalize="none"
autoCorrect={false}
//data to show in suggestion
data={games.length === 1 && comp(query, games[0].name) ? [] : games}
//default value if you want to set something in input
defaultValue={query}
/*onchange of the text changing the state of the query which will trigger
the findGame method to show the suggestions*/
onChangeText={text => this.setState({ query: text })}
placeholder=" Adicione os jogos que você tem"
//This below is the 'list' of autocomplete options
renderItem={({ item }) => (
//you can change the view you want to show in suggestion from here
//I GET ERROR WHEN TRYING TO ERASE (PS4) IN TEXT BOX ***NEED TO CHECK THIS
<View style={styles.iconContainer} >
<TouchableOpacity onPress={() => this.setState({ query: item.name})}
style={styles.autocompleteList} >
<View>
<Image
style={styles.gameImage}
source={{uri: item.background_image}}
/>
</View>
<Text style={styles.gameText}>
`${item.name}`
</Text>
</TouchableOpacity>
</View>
)}
/>
</Header>
</View>
<TouchableOpacity
style={styles.addButton}
onPress={() => this.AddItemsToArray()}
>
<Entypo name="plus" size={50} color="#fff" />
</TouchableOpacity>
</View>
</TouchableWithoutFeedback>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#fff',
flex: 1,
},
autocompleteInput: {
borderWidth: 1,
backgroundColor: "#fff",
borderColor: '#7843FF',
height: 50,
marginTop: 70,
borderRadius:10,
},
autocompleteList: {
flex:1,
flexDirection: 'row',
borderWidth:0.5,
borderColor: '#7843FF',
paddingVertical: 5,
paddingRight: 60,
},
listContainer: {
flex: 1,
position: 'absolute',
left: 10,
right: 10,
top:150,
flexDirection:'column',
justifyContent: 'center',
borderColor: '#7843FF',
},
gameText: {
fontSize: 15,
marginLeft: 10,
marginRight:30,
marginVertical:10,
color: '#000',
textAlign: 'left',
justifyContent: 'center'
},
usergameText: {
fontSize:15,
textAlign: 'left',
alignSelf:'stretch',
color: '#000',
},
gameImage: {
flex: 3,
height: 60,
width: 60,
marginLeft:10,
borderRadius: 100,
},
listImage: {
flex: 3,
height: 110,
width: 90,
marginLeft:0,
},
addButton: {
height:50,
width: 50,
position: 'absolute',
left: 371,
top: 71,
backgroundColor: '#7843FF',
borderTopRightRadius: 10,
borderBottomRightRadius:10,
},
usergameImage: {
height: 100,
width: 100,
borderRadius: 100,
},
header: {
backgroundColor:'#67E6DC'
}
});
export default App;

React Native: Stop endlesss loop of onEndReached of FlatList when empty json array is retuned from rest api

I am using the infinite scrolling Flatlist pattern of react-native mentioned on the internet. So far it's good for having long data. But when the rest API pagination completed, it returns the empty JSON array. Now with infinite scrolling pattern, onEndReached function is infinitely thrown leading to unnecessary rest call. So what's the best way to deal with this situation.
The data from API could be a single row or 500 rows of JSON object depending upon the conditional URL mentioned in code. In the case of 500 rows, it's fine till there's more data to fetch but after the last 480-500 batch, it becomes an infinite loop, which is a problem. In the case of 1 row, it immediately becomes an endless loop. How could I break the onEndReached event conditionally so that it never trigger when I detect empty array from rest api.
Below is my Flatlist implementation:
import React, { Component } from 'react';
import { StyleSheet, Text, View, FlatList, Image, ActivityIndicator, TouchableOpacity, ToastAndroid } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import GLOBAL from './productglobal'
import { Ionicons } from '#expo/vector-icons';
import SearchBar from '../commoncomponents/searchbar'
export default class ProductsList extends Component {
static navigationOptions = ({ navigation }) => {
return {
headerTitle: 'Products',
headerRight: () => (
<TouchableOpacity
style={{ paddingRight: 20 }}
onPress={() => { navigation.navigate("Settings") }}
>
<Ionicons name='md-more' size={25} color='white' />
</TouchableOpacity>
),
}
};
constructor(props) {
super(props);
this.state = {
loading: false,
searchValue: '',
data: [],
page: 1,
error: null,
refreshing: false,
base_url: null,
c_key: null,
c_secret: null,
};
GLOBAL.productlistScreen = this;
}
async componentDidMount() {
await this.getCredentials();
this.fetchProductList();
}
getCredentials = async () => {
const credentials = await SecureStore.getItemAsync('credentials');
const credentialsJson = JSON.parse(credentials)
this.setState({
base_url: credentialsJson.base_url,
c_key: credentialsJson.c_key,
c_secret: credentialsJson.c_secret,
})
}
fetchProductList = () => {
const { base_url, c_key, c_secret, page, searchValue } = this.state;
let url = null
if (searchValue) {
url = `${base_url}/wp-json/wc/v3/products?per_page=20&search=${searchValue}&page=${page}&consumer_key=${c_key}&consumer_secret=${c_secret}`;
} else {
url = `${base_url}/wp-json/wc/v3/products?per_page=20&page=${page}&consumer_key=${c_key}&consumer_secret=${c_secret}`;
}
console.log(url)
this.setState({ loading: true });
setTimeout(() => {
fetch(url).then((response) => response.json())
.then((responseJson) => {
this.setState({
data: this.state.data.concat(responseJson),
error: responseJson.code || null,
loading: false,
refreshing: false
});
}).catch((error) => {
this.setState({
error,
loading: false,
refreshing: false
})
});
}, 1500);
};
renderListSeparator = () => {
return (
<View style={{
height: 1,
width: '100%',
backgroundColor: '#999999'
}} />
)
}
renderListFooter = () => {
if (!this.state.loading) return null;
return (
<View style={{
paddingVertical: 20,
}}>
<ActivityIndicator color='#96588a' size='large' />
</View>
)
}
handleRefresh = () => {
this.setState({
page: 1,
refreshing: true,
data: []
}, () => {
this.fetchProductList();
}
)
}
handleLoadMore = () => {
console.log('loading triggered')
this.setState({
page: this.state.page + 1,
}, () => {
this.fetchProductList();
})
}
handleSearch = (value) => {
// console.log(value)
this.setState({
searchValue: value,
page: 1,
refreshing: true,
data: []
}, () => {
this.fetchProductList()
})
}
render() {
return (
<View style={{flex:1}}>
<SearchBar onSearchPress={this.handleSearch}></SearchBar>
<FlatList
data={this.state.data}
keyExtractor={item => item.id.toString()}
refreshing={this.state.refreshing}
extraData={this.state.data}
onRefresh={this.handleRefresh}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={0.5}
ItemSeparatorComponent={this.renderListSeparator}
ListFooterComponent={this.renderListFooter}
renderItem={({ item }) =>
<TouchableOpacity onPress={() => {
this.props.navigation.navigate('ProductDetails', {
productId: item.id,
productName: item.name,
base_url: this.state.base_url,
c_key: this.state.c_key,
c_secret: this.state.c_secret
});
}}>
<View style={{ flex: 1, flexDirection: 'row', backgroundColor: 'white' }}>
<View style={{ flex: 1, justifyContent: "center", alignContent: "center" }}>
<Image source={(Array.isArray(item.images) && item.images.length) ?
{ uri: item.images[0].src } :
require('../../../assets/images/blank_product.png')}
onError={(e) => { this.props.source = require('../../../assets/images/blank_product.png') }}
style={{ height: 115, width: 115 }} resizeMode='contain' />
</View>
<View style={{ flex: 2, marginTop: 10, marginBottom: 10, justifyContent: "center" }}>
<View style={{ marginLeft: 10 }}>
<Text style={styles.titleText}>{item.name}</Text>
<Text>SKU: {item.sku}</Text>
<Text>Price: {item.price}</Text>
<Text>Stock Status: {item.stock_status}</Text>
<Text>Stock: {item.stock_quantity}</Text>
<Text>Status: {item.status}</Text>
</View>
</View>
</View>
</TouchableOpacity>
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
titleText: {
fontSize: 20,
fontWeight: 'bold',
}
});
You can add a state property hasMoreToLoad which defaults to true.
Then you can check in fetchProductList if the result is less than per_page (data < 20) IF the result is less than per_page you know you've reached the end and you can set hasMoreToLoad to false.
onEndReached={this.state.hasMoreToLoad ? this.handleLoadMore : null}
Just simply put condition on onEndReached() :
<FlatList
data={this.state.data}
keyExtractor={item => item.id.toString()}
refreshing={this.state.refreshing}
extraData={this.state.data}
onRefresh={this.handleRefresh}
onEndReached={this.status.data.length > 0 ? this.handleLoadMore : null} // change here
...
...
/>

display activated activity indicator on current row with react native

Currently working on an app this gives users lists of news on a page and on each news has a textbox where you can input your comment.
So for example, 10 news items will have 10 textboxes.
When a user comment after and hit the submit button, the activity indicator appears for all 10 news items, but I want it to only display on where the comment has been made and also after posting the comment, the comment box should be empty
Function
state = {
posts: [],
comment: ""
};
commentPost = item => {
const api = create({
baseURL: "patch-to-api-url",
headers: { Accept: "application/json" }
});
const self = this;
self.setState({ modalLoader: true });
api
.post("news/posts/" + `${item.id}` + "/comments", {
media: "",
text: this.state.comment
})
.then(response => {
console.log(response);
self.setState({ modalLoader: false });
//updating the state
this.setState(prevState => ({
posts: prevState.posts.map(el => {
if (el.id === item.id) {
return {
...el,
commentsCount: el.commentsCount + 1
};
}
return el;
})
}));
});
};
View
<ScrollView>
{posts.map((item, i) => {
return (
<View key={i} style={styles.user}>
<Card>
<ListItem
titleStyle={{ color: "#36c", fontWeight: "500" }}
onPress={() =>
navigation.navigate("PostComments", {
postID: item.id,
groupID: item.writer.group.id,
communityID: item.group.community.id
})
}
titleNumberOfLines={2}
hideChevron={false}
chevronColor="#36c"
roundAvatar
title={item.headline}
avatar={{
uri:
"https://s3.amazonaws.com/uifaces/faces/twitter/brynn/128.jpg"
}}
/>
<Text
style={{
marginBottom: 10,
fontSize: 16,
color: "#000",
fontFamily: "HelveticaNeue-Light"
}}
>
{item.text}
</Text>
<TextInput
onChangeText={onSetComment}
label="Write Comment"
underlineColor="#36a"
style={{ backgroundColor: "#fff", width: "90%" }}
/>
<View>
<Icon
name="md-send"
type="ionicon"
color="#999"
onPress={() => {
onCommentPost(item);
}}
/>
<View style={styles.loading}>
<ActivityIndicator animating={modalLoader} size="small" />
</View>
</View>
</Card>
</View>
);
})}
</ScrollView>
You don't have enough state to accomplish what you want. Wanting an independent spinner in each post implies that you have to store it's state somewhere.
You should add the modalLoader attribute to each post and not globally. Change your function to look like this:
commentPost = item => {
const api = create({
baseURL: "patch-to-api-url",
headers: { Accept: "application/json" }
});
const self = this;
self.setState({ posts: this.state.posts.map(post => post.id === item.id ? {...post, modalLoader: true } : post));
api
.post("news/posts/" + `${item.id}` + "/comments", {
media: "",
text: this.state.comment
})
.then(response => {
console.log(response);
self.setState({ posts: this.state.posts.map(post => post.id === item.id ? {...post, modalLoader: false } : post));
//updating the state
this.setState(prevState => ({
posts: prevState.posts.map(el => {
if (el.id === item.id) {
return {
...el,
commentsCount: el.commentsCount + 1
};
}
return el;
})
}));
});
};
And your component to look like this:
<ScrollView>
{posts.map((item, i) => {
return (
<View key={i} style={styles.user}>
<Card>
<ListItem
titleStyle={{ color: "#36c", fontWeight: "500" }}
onPress={() =>
navigation.navigate("PostComments", {
postID: item.id,
groupID: item.writer.group.id,
communityID: item.group.community.id
})
}
titleNumberOfLines={2}
hideChevron={false}
chevronColor="#36c"
roundAvatar
title={item.headline}
avatar={{
uri:
"https://s3.amazonaws.com/uifaces/faces/twitter/brynn/128.jpg"
}}
/>
<Text
style={{
marginBottom: 10,
fontSize: 16,
color: "#000",
fontFamily: "HelveticaNeue-Light"
}}
>
{item.text}
</Text>
<TextInput
onChangeText={onSetComment}
label="Write Comment"
underlineColor="#36a"
style={{ backgroundColor: "#fff", width: "90%" }}
/>
<View>
<Icon
name="md-send"
type="ionicon"
color="#999"
onPress={() => {
onCommentPost(item);
}}
/>
<View style={styles.loading}>
<ActivityIndicator animating={item.modalLoader} size="small" />
</View>
</View>
</Card>
</View>
);
})}
</ScrollView>
You are sharing the modalLoader state among all iterated posts. A Post should be a single stateful component. Then for the specific component that was updated you need to update only that state.

Resources