Search code examples
androidreact-nativesetintervalreact-native-flatlistcountdownjs.js

React Native Count up Timer inside FlatList


Hi I'm new to React Native and now I'm trying to create count up timer inside each Flat List row. The calculation based on data timestamp compare to current date. My countdown interval is working for each row but the problem now is performance. In Android sometimes is causing ANR (Application not responding).

Is there any suggestion that I can do to improve this code? Really appreciate for any kind help. Thank you.

  import React, { useState, useEffect } from 'react'
    import { View, Text, ActivityIndicator, Modal, FlatList } from 'react-native'
    import Style from '../../../../constants/Style'
    import AsyncStorage from '@react-native-community/async-storage'
    import { API, Auth } from 'aws-amplify'
    import AWSMQTTConnection from '../../../../common/AWSMQTTConnection'
    import AsyncStorageConstants from '../../../../constants/AsyncStorageConstants'
    
    var isLeaving = false
    
    
    function EquipmentElement({ id, name, number, status, time, maintenanceCode }) {
    
        const [hours, setHours] = useState('00')
        const [minutes, setMinutes] = useState('00')
        const [seconds, setSeconds] = useState('00')
    
        var count = setInterval(() => {
            var now = new Date().getTime()
            var distance = now - time
    
            var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
            var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60))
            var seconds = Math.floor((distance % (1000 * 60)) / 1000)
    
            var isHourLessThanTen = hours < 10
            var isMinuteLessThanTen = minutes < 10
            var isSecondLessThanTen = seconds < 10
    
            if (isHourLessThanTen) {
                hours = '0' + hours
            }
    
            if (isMinuteLessThanTen) {
                minutes = '0' + minutes
            }
    
            if (isSecondLessThanTen) {
                seconds = '0' + seconds
            }
    
    
            if ((status == 'Available' || status == undefined) || maintenanceCode == 1) {
                clearInterval(count)
            } else {
                if (isLeaving) {
                    clearInterval(count)
                } else {
                    setHours(hours)
                    setMinutes(minutes)
                    setSeconds(seconds)
                }
            }
    
        }, 1000)
    
    
        const setDurationValue = () => {
            if (maintenanceCode == 1) {
                <Text style={Style.value}>00:00:00</Text>
            } else {
                if (time != 0 || time != '' || time == undefined) {
                    return (<Text style={Style.value}>{hours}:{minutes}:{seconds}</Text>)
    
                } else {
                    return (<Text style={Style.value}>00:00:00</Text>)
                }
            }
        }
    
        return (
            <View style={Style.smartElementContainer}>          
                 {setDurationValue()}                  
            </View>
        )
    }
    
    function Equipment() {
    
        const [equipmentData, setEquipmentData] = useState([])
      
        useEffect(() => {
    
            isLeaving = false
            getEquipmentList(false)
    
            return function cleanup() {
                console.log('unmounting...')
                isLeaving = true
                setEquipmentData([])
            }
    
        }, [])

 const getEquipmentList = async (isMQTT) => {
        try {
           
            let propertyId = await AsyncStorage.getItem(AsyncStorageConstants.StorageConstants.CURRENT_PROPERTY_ID)
            let apiName = 'DemoAsiaIoT'
            let path = '/scdevice'
            let request = {
                headers: { Authorization: `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}` },
                response: true,
                queryStringParameters: {
                    propertyId: propertyId,
                    applicationId: 5
                }
            }

            await API.get(apiName, path, request).then(response => {
                var data = response.data.resultData
                var devices = []

                for (var i = 0; i < data.length; i++) {
                    var device = data[i]
                    devices.push({ id: device.deviceEUI, name: device.deviceName, number: '01', status: device.status, time: device.timestampStart, maintenanceCode: device.manualOverRide })
        
                }

                setEquipmentData(devices)
                
            }).catch(error => {
                console.log('Error from request - ', error.response)
            })


        } catch (err) {
            console.log('error:', err)
        }
    }

    
            return (
            <View style={{ flex: 1 }}>
                <FlatList
                    style={{
                        width: '100%',
                        marginTop: 10,
                    }}
                    showsVerticalScrollIndicator={false}
                    vertical={true}
                    data={equipmentData}
                    renderItem={({ item }) => <EquipmentElement id={item.id} name={item.name} number={item.number} status={item.status} time={item.time} maintenanceCode={item.maintenanceCode} />}
                    keyExtractor={item => item.id} />
            </View>
        )
    }
    
    export default Equipment

Solution

  • Few pointers on how you can optimize your code.

    1. You don't need to use three states to hours, minutes, and seconds to show your time in your FlatList item. Those are the derivation from Date.now(). You could check the code below to understand the concept. Simply create a helper function outside the scope of your component that returns your formatted time to show. Most of your performance can be solved if you do this.
    function calculateTimeLeft() {
      const year = new Date().getFullYear();
      const difference = +new Date(`${year}-10-1`) - +new Date();
      let timeLeft = {};
    
      if (difference > 0) {
        timeLeft = {
          days: Math.floor(difference / (1000 * 60 * 60 * 24)),
          hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
          minutes: Math.floor((difference / 1000 / 60) % 60),
          seconds: Math.floor((difference / 1000) % 60)
        };
      }
    
      return timeLeft;
    }
    
    function FlatListItem() {
      const [timeLeft, setTimeLeft] = React.useState(calculateTimeLeft());
    
      React.useEffect(() => {
        const id = setTimeout(() => {
          setTimeLeft(calculateTimeLeft());
        }, 1000);
    
        return () => {
          clearTimeout(id);
        };
      });
    
      const timerComponents = Object.keys(timeLeft).map(interval => {
        if (!timeLeft[interval]) {
          return;
        }
    
        return (
          <span>
            {timeLeft[interval]} {interval}{" "}
          </span>
        );
      });
    
    1. I have no idea why are setting isLeaving in your component. Remove it. It is of no use. clearTimeout on your return statement from useEffect.

    2. Use your styles from StyleSheet.create. Create a function keyExtractor and place it outside your component. And pass item as a prop.

    // outside the scope of your component
    function keyExtractor(item) {
      return item.id;
    }
    
    <FlatList
         style={styles.flatlist} // place your styles in `StyleSheet.create`
         showsVerticalScrollIndicator={false}
         vertical={true}
         data={equipmentData}
         renderItem={EquipmentElement} // pass the item as a prop
         keyExtractor={keyExtractor} 
    />