'State is not updated in useEffect hook with Fetch method
I'm unable to set the state for setTimeForInactivityInSecond from the Fetch method response.
If I initialize [timeForInactivityInSecond, setTimeForInactivityInSecond] = useState(20) for example with a number in the beginning it works but I need to get the data from the fetch method and update the state. This is in my App.js file.
const App: () => React$Node = () => {
const [timeForInactivityInSecond, setTimeForInactivityInSecond] = useState('')
useEffect(() => {
fetch('https://someserver.com/wine/php/router.php?action=get_ads_timeout')
.then((response) => response.json())
.then((data) => {
let adsTimeout = parseInt(data.timeout);
console.log(adsTimeout)
setTimeForInactivityInSecond(adsTimeout)
})
.catch((error) => {
console.log("something is wrong")
});
resetInactivityTimeout()
},[timeForInactivityInSecond])
const panResponder = React.useRef(
PanResponder.create({
onStartShouldSetPanResponderCapture: () => {
// console.log('user starts touch');
console.log("what seconds: " + timeForInactivityInSecond);
setModalVisible(false)
resetInactivityTimeout()
},
})
).current
const resetInactivityTimeout = () => {
clearTimeout(timerId.current)
timerId.current = setTimeout(() => {
// action after user has been detected idle
if(loggedIn == false){
setModalVisible(false)
} else {
setModalVisible(true)
// navigationRef.current?.navigate('Home');
}
}, timeForInactivityInSecond * 1000)
}
useEffect(() => {
const timer = setInterval(() => {
currentIndex.current = currentIndex.current === ads.length - 1
? 0
: currentIndex.current + 1;
myRef?.current?.scrollToIndex({
animated: true,
index: currentIndex.current ,
});
}, 20000);
return () => clearInterval(timer);
}, []);
}
Full Code:
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
/*================
npx react-native start
npx react-native run-android
================*/
import 'react-native-gesture-handler';
import React, {useState, useEffect, useRef, useCallback} from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { useNavigation } from '@react-navigation/native';
import { createStackNavigator, HeaderBackButton } from '@react-navigation/stack';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
StatusBar,
ImageBackground,
Image,
TextInput,
Button,
TouchableNativeFeedback,
TouchableWithoutFeedback,
TouchableOpacity,
Modal,
Pressable,
PanResponder,
FlatList,
Dimensions
} from 'react-native';
import { Immersive } from 'react-native-immersive';
import {
Header,
LearnMoreLinks,
Colors,
DebugInstructions,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
import WineList from './screens/WineList';
import Home from './screens/Home';
import Rate from './screens/Rate';
import Thankyou from './screens/Thankyou';
import LoginScreen from './screens/LoginScreen';
const Stack = createStackNavigator();
const { width: windowWidth, height: windowHeight } = Dimensions.get("window");
let ads = '';
fetch('https://someserver.com/wine/php/router.php?action=get_winery_ads')
.then((response) => response.json())
.then((data) => {
// let jsonData = JSON.stringify(data)
ads = data;
// getQrCodeUrl(data)
})
.catch((error) => {
console.log("something is wrong with winery ads")
});
const App: () => React$Node = () => {
Immersive.on()
Immersive.setImmersive(true)
const navigationRef = useRef(null);
const myRef = useRef(null);
const currentIndex = useRef(0);
const [modalVisible, setModalVisible] = useState(false);
// changing to true will allow the ads to show after the set time. set to false to disable so you can develop
const [loggedIn, setLoggedIn] = useState(true);
const timerId = useRef(false);
const [timeForInactivityInSecond, setTimeForInactivityInSecond] = useState('')
useEffect(() => {
fetch('https://some.com/wine/php/router.php?action=get_ads_timeout')
.then((response) => response.json())
.then((data) => {
let adsTimeout = parseInt(data.timeout);
console.log(adsTimeout)
setTimeForInactivityInSecond(adsTimeout)
})
.catch((error) => {
console.log("something is wrong with winery ads")
});
resetInactivityTimeout()
},[])
const panResponder = React.useRef(
PanResponder.create({
onStartShouldSetPanResponderCapture: () => {
// console.log('user starts touch');
console.log("what seconds: " + timeForInactivityInSecond);
setModalVisible(false)
resetInactivityTimeout()
},
})
).current
const resetInactivityTimeout = () => {
clearTimeout(timerId.current)
timerId.current = setTimeout(() => {
// action after user has been detected idle
if(loggedIn == false){
setModalVisible(false)
} else {
setModalVisible(true)
// navigationRef.current?.navigate('Home');
}
}, timeForInactivityInSecond * 1000)
}
useEffect(() => {
const timer = setInterval(() => {
currentIndex.current = currentIndex.current === ads.length - 1
? 0
: currentIndex.current + 1;
myRef?.current?.scrollToIndex({
animated: true,
index: currentIndex.current ,
});
}, 20000);
return () => clearInterval(timer);
}, []);
// maybe move adslider stuff to home screen
function AdSlider({data}){
return(
<View style={{alignContent:'center', alignItems:'center', backgroundColor:'#4B4239', width:663}}>
<Image source={{uri: data.adImg}} style={{width:663,height:500}} ></Image>
<Text style={{color:'white', fontFamily:'LaoMN', fontSize:25, marginTop:20, textAlign:'center', paddingRight:40, paddingLeft:40}}>{data.adTitle}</Text>
<Text style={{color:'white', fontFamily:'LaoMN', fontSize:20, marginTop:20, textAlign:'center', paddingRight:25}} > {data.adInfo} </Text>
<View style={{flexDirection:'row', justifyContent:'flex-start', alignContent:'center', alignItems:'center', marginTop:20}}>
<Text style={{fontSize:40, color:'white', padding:20}}>Scan Here </Text>
<Image style={{width:200,height:200}} source={{uri: data.qrCodeImg}}></Image>
</View>
</View>
)
}
return (
<NavigationContainer ref={navigationRef} >
<View {...panResponder.panHandlers} style={{ flex:1}}>
<TouchableWithoutFeedback >
<Modal
animationType="slide"
transparent={false}
hardwareAccelerated={false}
visible={modalVisible}
>
<FlatList
ref={myRef}
data={ads}
renderItem={({ item, index }) => {
return <AdSlider key={index} data={item} dataLength={ads.length} />;
}}
initialScrollIndex={currentIndex.current}
onScrollToIndexFailed={item => {
const wait = new Promise(resolve => setTimeout(resolve, 500));
wait.then(() => {
myRef?.current?.scrollToIndex({ index: currentIndex.current, animated: true });
});
}}
pagingEnabled
horizontal
showsHorizontalScrollIndicator={false}
/>
</Modal>
</TouchableWithoutFeedback>
<Stack.Navigator initialRouteName="LoginScreen" navigationOptions={{headerTintColor: '#ffffff',}} screenOptions={{
headerTintColor: '#ffffff',
cardStyle: { backgroundColor: '#4B4239' },
}} >
<Stack.Screen name="LoginScreen"
component={LoginScreen} options={{
headerShown: false,
}} />
<Stack.Screen name="Home"
component={Home} options={{
headerShown: false,
}} />
<Stack.Screen name="WineList" component={WineList} options={{
title: 'Exit',
headerStyle: {
backgroundColor: '#4B4239',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}/>
<Stack.Screen name="Rate" component={Rate} options={{
title: 'Back to Selections',
headerStyle: {
backgroundColor: '#4B4239',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}/>
<Stack.Screen name="Thankyou" component={Thankyou}
options={
{
headerShown: false,
title: 'Home',
headerStyle: {
backgroundColor: '#4B4239',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}/>
</Stack.Navigator>
</View>
</NavigationContainer>
);
};
export default App;
Solution 1:[1]
As for your comment: 'The issue is I need to get the number of seconds from a database and use it to set the timeForInactivityInSecond when the app first loads', remove timeForInactivityInSecond from your dependency array and this useEffect() runs only when the app first loads:
useEffect(() => {
fetch('https://someserver.com/wine/php/router.php?action=get_ads_timeout')
.then((response) => response.json())
.then((data) => {
let adsTimeout = parseInt(data.timeout);
console.log(adsTimeout)
setTimeForInactivityInSecond(adsTimeout)
})
.catch((error) => {
console.log("something is wrong")
});
resetInactivityTimeout()
},[]) // removed timeForInactivityInSecond
Solution 2:[2]
None of the answers were able to solve the issue. I need to initialize and set the state when the app first runs:
const [timeForInactivityInSecond, setTimeForInactivityInSecond] = useState(adsTimeout) adsTimeout does not work when setting the state initially. How can I set the state using adsTimeout even though I have to wait for the fetch.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | ABDULLOKH MUKHAMMADJONOV |
| Solution 2 | oxxi |
