setState when fetch data that leads Render in loop - reactjs

The problem is when i create paginaion button, the render is running in loop.
As i call api from external that i can store all data in dataset. I know something went wrong but i am not able to solve it personally, so can any one suggest solutions to help improve? Thanks.
SearchScreen.js
componentDidMount() {
this.setState({isLoading: true},function(){
this.fetchData(this.url)
});
}
async fetchData(query, pageNo) {
try {
let response = await fetch(query);
let responseText = await response.json();
let json = await this.getData(responseText, pageNo);
} catch(error) {
console.error(error);
}
}
async getData(responseText, pageNo){
this.setState({
data: responseText.response.listings,
isLoading: false,
currentPage: pageNo,
lastPage: responseText.response.total_pages
});
}
PropertyList(arr, navigate) {
return arr.map(function(property, i){
return(
<TouchableHighlight style={styles.button} underlayColor='#99d9f4'
key={i} onPress={() => navigate('PropertyView', {property: property})} >
<View key={i} style={styles.rowContainer}>
<Image style={styles.thumb} source={{ uri: property.img_url }} />
<View style={styles.textContainer}>
<Text style={styles.price}>{property.price_formatted}</Text>
<Text style={styles.title}
numberOfLines={2}>{property.title}</Text>
</View>
</View>
</TouchableHighlight>
);
});
}
SetPage = (pageNo) =>{
this.url = this.GetChangedUrl(pageNo);
this.fetchData(this.url, pageNo);
}
GetChangedUrl = (pageNo) =>{
return this.url.replace(/(page=\d+&)/g, 'page='+pageNo+ '&');
}
_formArray = (pageNo) =>{
if (pageNo == 1){
return [pageNo, pageNo+1, pageNo+2];
}else if (pageNo == this.lastPage){
return [pageNo-2, pageNo-1, pageNo];
}else{
return [pageNo-1, pageNo, pageNo+1];
}
}
_createPageButton = (currentPage) =>{
var array = this._formArray(currentPage);
var btnBody = array.map( (item, i) => {
return (
<TouchableHighlight style={styles.pageButton} key={i} Onpress={this.SetPage(i)}>
<Text style={styles.text} >{i + 1}</Text>
</TouchableHighlight>
)
});
return btnBody;
}
render() {
const { navigate } = this.props.navigation;
const { params } = this.props.navigation.state;
this.url = params.url;
let propertyList;
if (this.state.isLoading){
propertyList = (
<ActivityIndicator size={108} style= {styles.indicator}/>
)
}else{
propertyList = (
<View >
<View >
<ScrollView >
{this.PropertyList(this.state.data, navigate)}
<View style={styles.separator}/>
<View style={styles.pageContainer}>
{this._createPageButton(this.currentPage)}
</View>
</ScrollView>
</View>
</View>
)
}
return (
<View>
{propertyList}
</View>
)
}
}
Whole Code from here

I believe this is caused by:
Onpress={this.SetPage(i)}
which should be changed to:
Onpress={() => this.SetPage(i)}
Your code calls setPage when rendering and passes to onPress the result of the call, causing change of page, and therefore a new load, on every render.

Related

Using an API response to trigger a modal

I'm attempting to use the response from my API (the response being a number ranging from -3 to +3) to trigger a modal. I receive a response from the API and store the number in a state var setModalVisible which I thought would cause the modal to appear however it does not. Eventually I would like varying modals to appear dependent upon the number, i.e if (response < 3) { certainModal } else { } however I can't get this initial one to render.
Below is a snippet containing the most relevant code:
async function makePostRequest (diaryText) {
let payload = { description: diaryText };
let response = await axios.post('http://localhost:3000/', payload);
let data = response.data.replace(/[^0-9+\/*=\-.\s]+/g, '');
return data;
}
makePostRequest(diaryText)
.then(response => {
this.setState({ setModalVisible: response });
})
.catch(error => {
error.message;
})
const [modalVisible, setModalVisible] = useState(false);
return (
<View style={styles.centeredView}>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
Alert.alert("Modal has been closed.");
setModalVisible(!modalVisible);
}}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text style={styles.modalText}>Manderley thinks you are feeling good/okay/bad, is this correct?</Text>
<Pressable
style={[styles.YesButton, styles.YesButtonClose]}
onPress={() => setModalVisible(!modalVisible)}
>
<Text style={styles.textStyle}>Yes</Text>
</Pressable>
<Pressable
style={[styles.NoButton, styles.NoButtonClose]}
onPress={() => setModalVisible(!modalVisible)}
>
<Text style={styles.textStyle}>No</Text>
</Pressable>
</View>
</View>
</Modal>
</View>
)
I think the idea is you just need to check the value after you get the API response.
Let's say you have a function:
function checkAPIValue() {
const apiResponse = functionToCallAPI()
if (apiResponse.response < 3){
setModalVisible(true) // Here you change the value of your modalVisible
}
}

React Native Expo AV - Implementing SeekBar

I am attempting to use react-native-slider with Expo AV to create a seekbar, but am having trouble updating the 'value' state of slider. When I try to set it to currentPosition/durationPosition, it errors out, likely because initially these values are NaN. I CAN display current/duration however.
My best guess is that I need a way to wait until my mp3 is loaded before rendering the SeekBar. I probably also need to do a better job of separating components and keep PlayerScreen very minimal. I've messed around with this code so much I can barely remember what I've tried... Getting close to ditching Expo because react-native-track-player looks easier to work with and I've heard some bad things about Expo. Anyways, here's where I'm at now
export default class PlayerScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
isPlaying: false,
playbackObject: null,
volume: 1.0,
isBuffering: false,
paused: true,
currentIndex: 0,
durationMillis: 1,
positionMillis:0,
sliderValue:0,
isSeeking:false,
}
}
async componentDidMount() {
try {
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
playsInSilentModeIOS: true,
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
shouldDuckAndroid: true,
staysActiveInBackground: true,
playThroughEarpieceAndroid: true
})
this.loadAudio()
} catch (e) {
console.log(e)
}
}
async loadAudio() {
const { currentIndex, isPlaying, volume} = this.state
try {
const playbackObject = new Audio.Sound()
const source = {
uri: this.props.route.params.item.uri
}
const status = {
shouldPlay: isPlaying,
volume,
}
playbackObject.setOnPlaybackStatusUpdate(this.onPlaybackStatusUpdate)
await playbackObject.loadAsync(source, status, true)
this.setState({playbackObject})
var sliderValue = this.state.positionMillis/this.state.durationMillis
} catch (e) {
console.log(e)
}
}
handlePlayPause = async () => {
const { isPlaying, playbackObject } = this.state
isPlaying ? await playbackObject.pauseAsync() : await playbackObject.playAsync()
this.setState({
isPlaying: !isPlaying
})
}
onPlaybackStatusUpdate = status => {
this.setState({
isBuffering: status.isBuffering,
durationMillis: status.durationMillis,
positionMillis: status.positionMillis,
})
}
render() {
const { item } = this.props.route.params;
return (
<View style={globalStyles.container}>
<Header />
<View style={globalStyles.subHeader}>
<Text style={globalStyles.title}>{ item.title }</Text>
</View>
<View style={styles.text}>
<Text>{ item.text }</Text>
</View>
<SeekBar
durationMillis={this.state.durationMillis}
positionMillis={this.state.positionMillis}
sliderValue={this.state.sliderValue}
/>
And here's the SeekBar component:
const SeekBar = ({
positionMillis,
durationMillis,
sliderValue
}) => {
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<View style={{ flex: 1 }} />
<Text style={[styles.text, { width: 40 }]}>
{positionMillis + ' / ' + durationMillis}
</Text>
</View>
<Slider
minimumValue={0}
maximumValue={1}
value={sliderValue}
style={styles.slider}
minimumTrackTintColor='#fff'
maximumTrackTintColor='rgba(255, 255, 255, 0.14)'
/>
</View>
);
};
export default SeekBar;
put
<SeekBar
durationMillis={this.state.durationMillis}
positionMillis={this.state.positionMillis}
sliderValue={this.state.sliderValue}
/>
in the screen component and
const SeekBar = ({
positionMillis,
durationMillis,
sliderValue
}) => {
sliderValue = positionMillis/durationMillis
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<View style={{ flex: 1 }} />
in the SeekBar component

[Unhandled promise rejection: TypeError: undefined is not an object (evaluating 'currentUser.uid')]

i get this warning when i try to store details to firebase using the currently logged in user.
i followed a online tutorial but i get this problem, is there any alternate method to use to achieve this?
i read about this problem but couldn't find a fix, i saw that its due to the user not being logged in but i did log in
var currentUser
class DecodeScreen extends Component {
addToBorrow = async (booktitle, bookauthor, bookpublisher, bookisbn) => {
currentUser = await firebase.auth().currentUser
var databaseRef = await firebase.database().ref(currentUser.uid).child('BorrowedBooks').push()
databaseRef.set({
'title': booktitle,
'author': bookauthor,
'publisher': bookpublisher,
'isbn': bookisbn
})
}
state = {
data: this.props.navigation.getParam("data", "NO-QR"),
bookData: '',
bookFound: false
}
_isMounted = false
bookSearch = () => {
query = `https://librarydb-19b20.firebaseio.com/books/${9781899606047}.json`,
axios.get(query)
.then((response) => {
const data = response.data ? response.data : false
console.log(data)
if (this._isMounted){
this.setState({ bookData: data, bookFound: true })
}
})
}
renderContent = () => {
if (this.state.bookFound) {
return(
<View style={styles.container2}>
<View style={styles.htext}>
<TextH3>Title :</TextH3>
<TextH4>{this.state.bookData.title}</TextH4>
</View>
<View style={styles.htext}>
<TextH3>Author :</TextH3>
<TextH4>{this.state.bookData.author}</TextH4>
</View>
<View style={styles.htext}>
<TextH3>Publisher :</TextH3>
<TextH4>{this.state.bookData.publisher}</TextH4>
</View>
<View style={styles.htext}>
<TextH3>Isbn :</TextH3>
<TextH4>{this.state.bookData.isbn}</TextH4>
</View>
</View>
)
}
else {
return(
<View style={styles.loading}>
<ActivityIndicator color="blue" size="large" />
</View>
)
}
}
componentDidMount(){
this._isMounted = true
}
componentWillUnmount(){
this._isMounted = false
}
render() {
{this.bookSearch()}
return (
<View style={styles.container}>
{this.renderContent()}
<Button title='Borrow' onPress={() => this.addToBorrow(this.state.bookData.title, this.state.bookData.author, this.state.bookData.publisher, this.state.bookData.isbn)} />
</View>
);
}
}
First, firebase.auth().currentUser is not a promise, and you can't await it. It's either null or a user object.
Second, it's not guaranteed to be populated with a user object on immediate page load. You will need to use an auth state observer to know when the user object first becomes available after a page load.

action is being dispatched before the state updates

After authentication I am redirected to the dashboard page where I call 3 APIs as soon as I land and then I display the data on it. I am getting the siteId from authentication page and is available in redux store and set that siteId to dashboard page's state. The issue is my action is firing before my state gets the value from redux. MapStateToProps is firing the action with undefined. After the action is fired, the state is being set. If I hard code the state, it works. I tried to check if my state is set before the action is fired, but seems like my check is not working.
class Dashboard extends Component {
constructor(props) {
super(props)
this.state = {
alarmCount: [],
workOrderCount: [],
etaWorkOrder: [],
countNum: true,
siteId: this.props.storeNum && this.props.storeNum.siteId
// siteId: 5260
}
}
componentDidMount() {
this.props.getOpenWorkOrdersCount();
this.props.getEtaWorkOrder();
}
componentWillReceiveProps({ alarmCount, workOrderCount, etaWO, storeNum }) {
if(alarmCount.code) {
this.setState({ alarmCount: alarmCount.code.data.ticketCount })
}
if(workOrderCount) {
this.setState({ workOrderCount: workOrderCount.data.event.woOutstanding })
}
if(etaWO) {
this.setState({ etaWorkOrder: etaWO.data.woList })
}
if(storeNum && storeNum.siteId && !(alarmCount.fetching || alarmCount.success) && !this.state.alarmCount.length){
this.props.getAlarmCount({siteId: storeNum.siteId});
}
}
const mapStateToProps = state => {
return {
alarmCount: state.alarmCount,
workOrderCount: state.workOrderCount.count,
etaWO: state.etaWorkOrder.count,
storeNum: state.ssoReducer.user
}
}
const mapDispatchToProps = dispatch => ({
getAlarmCount: data => dispatch(AlarmCountActions.getAlarmCount({ data }))
});
export default connect( mapStateToProps, mapDispatchToProps)(Dashboard);
This is the full component code:
class Dashboard extends Component {
constructor(props) {
super(props)
this.state = {
alarmCount: [],
workOrderCount: [],
etaWorkOrder: [],
countNum: true,
// siteId: props.storeNum && props.storeNum.siteId
siteId: ''
}
console.log('<<< storeNum', props.storeNum);
}
componentDidMount() {
// this.props.getOpenWorkOrdersCount();
// this.props.getEtaWorkOrder();
// if(this.props.storeNum){
// this.props.getAlarmCount(this.props.storeNum.siteId);
// }
}
// componentWillReceiveProps({ alarmCount, workOrderCount, etaWO, storeNum }) {
// if(alarmCount.code) {
// this.setState({ alarmCount: alarmCount.code.data.ticketCount })
// }
// if(workOrderCount) {
// this.setState({ workOrderCount: workOrderCount.data.event.woOutstanding })
// }
// if(etaWO) {
// this.setState({ etaWorkOrder: etaWO.data.woList })
// }
// if(storeNum){
// this.setState({ siteId: storeNum.siteId })
// }
// }
componentDidUpdate(prevProps) {
console.log('prevProps: ', prevProps)
if(prevProps.storeNum.siteId !== this.props.storeNum.siteId){
this.props.getAlarmCount(this.props.storeNum.siteId);
}
}
todaysDateFormatted = () => {
let today = new Date();
let dd = today.getDate();
let mm = today.getMonth() + 1;
let yyyy = today.getFullYear();
if (dd < 10) { dd = '0' + dd }
if (mm < 10) { mm = '0' + mm }
return today = mm + '-' + dd + '-' + yyyy;
}
howManyToday = data => {
let count = 0;
let todaysDate = this.todaysDateFormatted();
data.map(item => {
date = item.eta.replace(/(\d{4})\-(\d{2})\-(\d{2}).*/, '$2-$3-$1')
if(todaysDate == date) count++;
})
return count;
}
render() {
const navigation = this.props.navigation;
const woCount = this.state.workOrderCount
console.log(' ^^^^^^^ storeNuM: ', this.state.siteId)
return (
<View style={styles.container}>
<View style={styles.innerBox}>
<View style={styles.leftBox}>
<TouchableOpacity
style={styles.activeAlarms}
onPress={() => navigation.navigate(routes.ALARMS)}>
<View style={{ justifyContent: 'flex-end', flex: 1, background: 'transparent'}}>
{this.state.countNum ? (
<Fragment>
<Text style={[styles.blueColor, styles.font_24, ]}>{this.state.alarmCount}</Text>
</Fragment>
) : null}
<Text style={[styles.blueColor, styles.font_16] }>Active Alarms</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
style={styles.openWorkOrders}
onPress={() => navigation.navigate(routes.WORK_ORDERS)}>
<View style={{ justifyContent: 'flex-end', flex: 1 }}>
<Text style={[styles.whiteColor, styles.font_24]}>{woCount}</Text>
<Text style={[styles.whiteColor, styles.font_16]}>Open{'\n'}Work Orders</Text>
</View>
</TouchableOpacity>
</View>
<View style={styles.rightBox}>
<TouchableOpacity
style={styles.todayWorkOrders}
onPress={() => navigation.navigate(routes.WORK_ORDERS)}
>
<View style={{ justifyContent: 'flex-end', flex: 1 }}>
<Text style={[styles.whiteColor, styles.font_24]}>{this.howManyToday(this.state.etaWorkOrder)}</Text>
<Text style={[styles.whiteColor, styles.font_16]}>Today ETA{'\n'}Work Orders</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
style={styles.diyTrainingVideos}
onPress={() => navigation.navigate(routes.TRAINING_VIDEOS)}
>
<View style={{ justifyContent: 'flex-end', flex: 1 }}>
<Text style={[styles.blueColor, styles.font_16]}>DIY{'\n'}Training Videos</Text>
</View>
</TouchableOpacity>
</View>
</View>
<View style={styles.createWorkOrderBox}>
<TouchableOpacity
style={styles.createBtn}
onPress={() => navigation.navigate(routes.CREATE_WORK_ORDER)}>
<Text style={styles.workOrderText}>+ WORK ORDER</Text>
</TouchableOpacity>
</View>
</View>
)
}
}
const mapStateToProps = state => {
return {
alarmCount: state.alarmCount,
workOrderCount: state.workOrderCount.count,
etaWO: state.etaWorkOrder.count,
storeNum: state.ssoReducer.user
}
}
const mapDispatchToProps = dispatch => ({
getEtaWorkOrder: () => dispatch(etaWorkOrderActions.etaWorkOrder()),
getOpenWorkOrdersCount: () => dispatch(WorkOrderCountActions.count()),
getAlarmCount: data => dispatch(AlarmCountActions.getAlarmCount({ data })),
logoutUser: () => dispatch(logoutAction.logoutUser())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Dashboard);
What is wrong with my check? ComponentWillReceiveProps is firing the action now, I had ComponentDidMount fire the action before, that did not work either. How can I make sure that my state is set before this.props.getAlarmCount({siteId: storeNum.siteId}) is fired?

"Can only update a mounted or mounting component" warning comes up while using a setInterval

I am developing an App with React Native. I need to retrieve some data using fetch every 30 seconds. My code works fine and it retrieves the data correctly every 30 seconds. My problem is that as soon as I redirect to another screen, I get the following warning:
Warning: Can only update a mounted or mounting component. This usually
means you called setState() on an unmounted component. This is a
no-op. Please check the code for the xxxxxxxxx component.
Here is my code:
dataGet() {
listColumnFetch(this).then(result => {
let ColumnData = result.list;
let ColumnDataArray = Object.keys(ColumnData).map((key) => { return ColumnData[key] });
console.log("serverDataArray:", this.ColumnDataArray);
this.setState({
ColumnData,
ColumnDataArray,
isLoading: false,
CurrentData: new Date(),
});
});
}
componentDidMount() {
this.dataGet();
this.interval = setInterval(() => this.dataGet(), 30000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
Although I did clearInterval in componentWillUnmount, the App gives me that warning every 30 seconds in other pages. It seems that the timer didn't stop and it is in background. Can you help me to solve this problem?
UPDATE:
Also here I try to redirect to another page. Here is the rest of my code:
onPressNew() {
this.props.navigation.navigate('RechargeElectricCar', {user: this.state.user, activeSection: 'NoChargeInProgress_2a'});
}
render() {
if (this.state.isLoading) {
return(
<View>
<ActivityIndicator size="large" color="#79b729"/>
</View>
);
}
return (
<View style={styles.container} ref="park-progress-ref">
<View style={styles.titleContainer}>
<Text style={styles.itemBold}>{I18n.t('queste_ricariche')}</Text>
</View>
<View style={ styles.rowSep } />
<View style={ styles.listContainer } >
<FlatList
ItemSeparatorComponent={ () => <View style={ styles.rowSep } /> }
horizontal={false}
data={this.state.result}
renderItem={
({item}) => (
<View style={styles.containerrow}>
<View style={styles.viewPark}>
<Text style={styles.itemBold}> {I18n.t('Data_e_ora_inizio')}: <Text style={styles.itemNormal}>{item.start}</Text></Text>
<Text style={styles.itemBold}> {I18n.t('Data_e_ora_termine')}: <Text style={styles.itemNormal}>{item.end}</Text></Text>
<Text style={styles.itemBold}> {I18n.t('Energia')}: <Text style={styles.itemNormal}>{item.energy_delivered} KWh</Text></Text>
<Text style={styles.itemBold}> {I18n.t('Colonna')}: <Text style={styles.itemNormal}>{item.column_id}</Text></Text>
<Text style={styles.itemBold}> {I18n.t('Costo_della_ricarica')}: <Text style={styles.itemNormal}>€ {item.amount}</Text></Text>
<Text style={styles.itemBold}> {I18n.t('Aggiornamento_del')}: <Text style={styles.itemNormal}>{this.currentTime()}</Text></Text>
</View>
<View style={styles.rowCenter}>
<Button label={I18n.t('Via_questa_ricarica')} color={defStyleValues.RechargeElectricCar} onPress={ () => {console.log("MARCO log"); this.onPressTopUp(item.column_id)} } />
</View>
</View>
)
}
keyExtractor={item => item.id}
/>
</View>
<View style={ styles.rowSep } />
<View style={ styles.buttonContainer } >
<FadeInView
duration={2000}
style={{ alignItems: 'center' }}>
<ButtonIcon_White onPress={ () => { this.onPressNew() }} label={I18n.t('Nuova_ricarica')} />
</FadeInView>
</View>
</View>
);
}
When you navigate to a new screen, the new screen will be pushed on top of the previous screen. Because of this the previous screen will not be unmounted. This is why your interval is not clearing.
What you can do is to set a variable or a state value before doing a redirection and then checking the value before doing another setState.
Another thing to consider is to changing the value when you come back to previous screen. To handle that you can pass a function as a parameter to next screen and run it when the next screen's componentWillUnmount like below.
Example
onPressNew() {
// set stop value before navigating
this.setState({ stop: true }, () => {
this.props.navigation.navigate('RechargeElectricCar', {
user: this.state.user,
activeSection: 'NoChargeInProgress_2a',
onBack: this.onBack // Added this param for changing state to false
});
});
}
onBack = () => {
this.setState({stop: false});
}
//....
dataGet() {
// check stop value before fetching
if(this.state.stop !== true) {
listColumnFetch(this).then(result => {
let ColumnData = result.list;
let ColumnDataArray = Object.keys(ColumnData).map((key) => { return ColumnData[key] });
console.log("serverDataArray:", this.ColumnDataArray);
this.setState({
ColumnData,
ColumnDataArray,
isLoading: false,
CurrentData: new Date(),
});
});
}
}
On next screen (RechargeElectricCar screen)
componentWillUnmount() {
this.props.navigation.state.params.onBack()
}
Problem is with your setState setting state when your component has unmounted.
componentDidMount() {
this.isMountedComp = true
}
dataGet() {
listColumnFetch(this).then(result => {
let ColumnData = result.list;
let ColumnDataArray = Object.keys(ColumnData).map((key) => { return ColumnData[key] });
console.log("serverDataArray:", this.ColumnDataArray);
if(this.isMountedComp) {
this.setState({
ColumnData,
ColumnDataArray,
isLoading: false,
CurrentData: new Date(),
});
});
}
}
componentWillUnMount() {
clearInterval(this.interval);
this.isMountedComp = false
}
This will remove the warning errors.

Resources