'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