How to setInterval for every 5 second render with React hook useEffect in React Native app?

49,748

Solution 1

You need to clear your interval,

useEffect(() => {
  const intervalId = setInterval(() => {  //assign interval to a variable to clear it.
    setState(state => ({ data: state.data, error: false, loading: true }))
    fetch(url)
      .then(data => data.json())
      .then(obj =>
        Object.keys(obj).map(key => {
          let newData = obj[key]
          newData.key = key
          return newData
        })
     )
     .then(newData => setState({ data: newData, error: false, loading: false }))
     .catch(function(error) {
        console.log(error)
        setState({ data: null, error: true, loading: false })
     })
  }, 5000)

  return () => clearInterval(intervalId); //This is important
 
}, [url, useState])

For more about cleanup functions in useEffect refer to this.

Solution 2

It might be both these things:

  • You need to clear up your interval
  • You need to not update state from your API callback if its unmounted.

Code:

useEffect(() => {
   let isMounted = true
   const intervalId = setInterval(() => {  //assign interval to a variaable to clear it
    setState(state => ({ data: state.data, error: false, loading: true }))
    fetch(url)
      .then(data => data.json())
      .then(obj =>
        Object.keys(obj).map(key => {
          let newData = obj[key]
          newData.key = key
          return newData
        })
     )
     .then(newData => {
        if(!isMounted) return  // This will cancel the setState when unmounted
        setState({ data: newData, error: false, loading: false })
     })
     .catch(function(error) {
        console.log(error)
        setState({ data: null, error: true, loading: false })
     })
   }, 5000)

   return () => {
       clearInterval(intervalId); //This is important
       isMounted = false // Let's us know the component is no longer mounted.
   }

}, [url, useState])

Might want, depending on your server response time, add a failsafe for pending queries (example, if you sent out a query and the next one launches before the first one returns...).

Solution 3

For React Hooks + Apollo to fetch data from a GraphQL server every 5 seconds. In this example, we logout the user in React if the user is not logged-in in the backend. (JWT token not valide anymore)

import React from 'react'
import gql from 'graphql-tag'
import { useApolloClient } from '@apollo/react-hooks'

export const QUERY = gql`
  query Me {
    me {
      id
    }
  }
`

const MyIdle = () => {
  const client = useApolloClient()

  React.useEffect(() => {
    async function fetchMyAPI() {
      try {
        await client.query({
          query: QUERY,
          fetchPolicy: 'no-cache',
        })
      } catch (e) {
        // Logout the user and redirect to the login page
      }
    }

    const intervalId = setInterval(() => {
      fetchMyAPI()
    }, 1000 * 5) // in milliseconds
    return () => clearInterval(intervalId)
  }, [client])

  return null
}
export default MyIdle

Share:
49,748
jocoders
Author by

jocoders

💠My passion is to build high quality mobile apps 📱with React Native, TypeScript & MobX.

Updated on December 27, 2021

Comments

  • jocoders
    jocoders over 2 years

    I have React Native app and I get data from API by fetch. I created custom hook that get data from API. And i need to re-render it every 5 seconds. For it I wrapped my custom hook to setInterval and after my app become work very slowly and when I navigate to another screen I get this error:

    Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

    Can you tell me please how can I solve this bug and which will be the best way to setInterval, because I think my way is not good.

    My custom hook:

    export const useFetch = url => {
      const [state, setState] = useState({ data: null, error: false, loading: true })
    
      useEffect(() => {
        setInterval(() => {
          setState(state => ({ data: state.data, error: false, loading: true }))
          fetch(url)
            .then(data => data.json())
            .then(obj =>
              Object.keys(obj).map(key => {
                let newData = obj[key]
                newData.key = key
                return newData
              })
            )
            .then(newData => setState({ data: newData, error: false, loading: false }))
            .catch(function(error) {
              console.log(error)
              setState({ data: null, error: true, loading: false })
            })
        }, 5000)
      }, [url, useState])
      useEffect(() => () => console.log('unmount'), [])
      return state
    }
    

    My Component:

    const ChartsScreen = ({ navigation }) => {
      const { container } = styles
      const url = 'https://poloniex.com/public?command=returnTicker'
      const { data, error, loading } = useFetch(url)
    
      const percentColorHandler = number => {
        return number >= 0 ? true : false
      }
    
      return (
        <View style={container}>
          <ProjectStatusBar />
          <IconsHeader
            dataError={false}
            header="Charts"
            leftIconName="ios-arrow-back"
            leftIconPress={() => navigation.navigate('Welcome')}
          />
          <ChartsHeader />
          <ActivityIndicator animating={loading} color="#068485" style={{ top: HP('30%') }} size="small" />
          <FlatList
            data={data}
            keyExtractor={item => item.key}
            renderItem={({ item }) => (
              <CryptoItem
                name={item.key}
                highBid={item.highestBid}
                lastBid={item.last}
                percent={item.percentChange}
                percentColor={percentColorHandler(item.percentChange)}
              />
            )}
          />
        </View>
      )
    }