Issue with AsyncStorage and Asynchronous Behavior in React Native Functional Component

26 views Asked by At

I'm encountering an issue related to asynchronous behavior in my React Native functional component that involves the use of AsyncStorage. I have a custom component for AsyncStorage operations, and I've also tried using AsyncStorage directly, but the problem persists.

Code:

I have a React Native functional component named EntryScreen, and I'm using AsyncStorage for handling user data. The component involves various asynchronous operations, including data retrieval and synchronization with Firebase.

/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-shadow */
/* eslint-disable react-native/no-inline-styles */
import React, {useEffect, useState} from 'react';
import {
  Text,
  TouchableOpacity,
  SafeAreaView,
  Modal,
  View,
  TextInput,
  FlatList,
  Alert,
} from 'react-native';
import AntDesign from 'react-native-vector-icons/AntDesign';
import DatePicker from 'react-native-date-picker';
import {Image, Switch} from '@rneui/base';
import {ScheduleNotification} from '../../utils/Notification/notification';
import {Dropdown} from 'react-native-element-dropdown';
import PushNotification from 'react-native-push-notification';
import styles from './styles';
import NotificationChannels from '../../utils/Notification/NotificationChannels';
import State from '../../../Store';
import AsyncStorage from '@react-native-async-storage/async-storage';
import CreateUserDB from '../../utils/db/CreateUserDB';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import database from '@react-native-firebase/database';
const EntryScreen = ({navigation}: any) => {
  // user var which comes from Async Storage
  const [user, setUser] = useState<any>();
  const modal = State.useState(state => state.alarmModal);
  const [date, setDate] = useState<number>(Date.now());
  const snooze = State.useState(state => state.snooze);
  const [alarmLabel, setAlarmLabel] = useState<string>('');
  const [data, setData] = useState<any>([]);
  const [updateId, setUpdateId] = useState<number>(0);
  const [channel, setChannel] = useState<string>('');
  const db = CreateUserDB(user?.username);

  useEffect(() => {
    initializeData();
    NotificationChannels.alarms_carpark();
    NotificationChannels.alarms_sneez();
    NotificationChannels.alarms_synth();
  }, []);
  useEffect(() => {
    db.createTable();
  }, []);

  useEffect(() => {
    db.getData(dataArr => setData(dataArr));
  }, [data]);

  useEffect(() => {
    PushNotification.localNotification({
      title: 'hello',
      message: 'hello',
      channelId: 'alarms_sneez',
    });
  }, []);
  useEffect(() => {
    const autoSyncFirebase = async () => {
      const userRef = database().ref(
        user?.authType === 'email' ? `/${user.username}` : `/${user.id}`,
      );

      await Promise.all(
        data.map(async (element: any) => {
          await db.deleteElement(element.id);
        }),
      );

      const snapshot = await userRef.once('value');
      const fbData = snapshot.val().data;

      await Promise.all(
        fbData.map(async (element: any) => {
          await db.setData(
            element.timestamp,
            element.title,
            element.isNotificationOn,
            element.isSnooze,
          );
        }),
      );
    };

    autoSyncFirebase();
  }, []);

  const initializeData = async () => {
    const retrievedUser = await AsyncStorage.getItem('user');
    if (retrievedUser) {
      setUser(JSON.parse(retrievedUser));
    }
  };

  const handleOnListPress = (item: any) => {
    State.update(state => {
      state.alarmModal = true;
    });
    setUpdateId(item.id);
    setDate(item.timestamp);
    setAlarmLabel(item.title);
  };

  const onCancelPress = () => {
    State.update(state => {
      state.alarmModal = false;
    });
    setDate(Date.now());
    setUpdateId(0);
    State.update(state => {
      state.snooze = false;
    });
  };

  const onSavePress = () => {
    const isNewAlarm = updateId === 0;
    isNewAlarm
      ? db.setData(date, alarmLabel, true, snooze)
      : db.updateAlarm(date, alarmLabel, snooze, updateId);

    setDate(Date.now());
    State.update(state => {
      state.alarmModal = false;
      state.snooze = false;
    });

    ScheduleNotification(new Date(date), alarmLabel, snooze, channel);
  };

  const onDeleteAlarmPress = () => {
    db.deleteElement(updateId);
    State.update(state => {
      state.alarmModal = false;
    });
    setUpdateId(0);
  };

  const firebaseSync = async () => {
    const userRef = await database().ref(
      user.authType === 'email' ? `/${user.username}` : `/${user.id}`,
    );
    if (data > 0) {
      userRef.update({data: data});
    } else {
      function syncFromFirebase() {
        data.forEach((element: any) => {
          db.deleteElement(element.id);
        });
        userRef.once('value').then(snapshot => {
          const fbData = snapshot.val().data;
          fbData.forEach((element: any) => {
            db.setData(
              element.timestamp,
              element.title,
              element.isNotificationOn,
              element.isSnooze,
            );
          });
        });
      }
      function syncData() {
        userRef.update({data: data});
      }
      Alert.alert('WARNING', 'DO YOU WANNA SYNC EMPTY DATA?', [
        {text: 'get synced data', onPress: syncFromFirebase},
        {text: 'sync data', onPress: syncData},
      ]);
    }
  };
  const renderItem = ({item}: any) => {
    const dateString = new Date(item.timestamp).toLocaleTimeString([], {
      hour12: true,
      hour: '2-digit',
      minute: '2-digit',
    });

    const handleSwitchToggle = (value: boolean, item: any) => {
      if (value) {
        db.updateNotificationValue(true, item.id);
        ScheduleNotification(
          new Date(item.timestamp),
          item.title,
          item.isSnooze,
          channel,
        );
      } else {
        db.updateNotificationValue(false, item.id);
        PushNotification.getScheduledLocalNotifications(list => {
          list.forEach(notification => {
            const itemTimestamp = new Date(item.timestamp).toLocaleTimeString(
              [],
              {hour: '2-digit', minute: '2-digit', hour12: true},
            );
            const notificationTimestamp = notification.date.toLocaleTimeString(
              [],
              {hour: '2-digit', minute: '2-digit', hour12: true},
            );

            if (itemTimestamp === notificationTimestamp) {
              PushNotification.cancelLocalNotification(notification.id);
            }
          });
        });
      }
    };

    return (
      <TouchableOpacity
        onPress={() => handleOnListPress(item)}
        style={[styles.listViewStyles]}>
        <View>
          <Text style={styles.mainDateString}>{dateString}</Text>
          <Text>{item.title}, Every Wednesday</Text>
        </View>
        <Switch
          value={!!item.isNotificationOn}
          onValueChange={value => handleSwitchToggle(value, item)}
        />
      </TouchableOpacity>
    );
  };

  return (
    <SafeAreaView style={styles.mainContainer}>
      <View style={styles.topBarInnerContainer}>
        <MaterialIcons
          name="sync"
          size={30}
          color={'white'}
          onPress={firebaseSync}
          style={styles.syncBtn}
        />

        <Image
          source={
            user?.photo
              ? {uri: user?.photo}
              : require('../../assets/images/google.png')
          }
          style={styles.profileImg}
          resizeMode="contain"
          onPress={() => {
            navigation.navigate('profile');
          }}
        />
      </View>

      <View style={styles.topBarContainer}>
        <Text style={styles.headerFont}>Alarms</Text>
        <TouchableOpacity
          style={styles.plusBtn}
          onPress={() =>
            State.update(state => {
              state.alarmModal = true;
            })
          }>
          <AntDesign name="plus" size={40} color={'orange'} />
        </TouchableOpacity>
      </View>
      <FlatList
        keyExtractor={item => item.id.toString()}
        renderItem={renderItem}
        data={data}
      />

      <Modal visible={modal} transparent={false} animationType="slide">
        <SafeAreaView style={styles.modalContainer}>
          <View style={styles.modalTopBar}>
            <TouchableOpacity
              style={styles.modalTopBtn}
              onPress={onCancelPress}>
              <Text style={styles.modalTopBtnText}>Cancel</Text>
            </TouchableOpacity>
            <Text style={{fontSize: 20}}>Set Timer</Text>
            <TouchableOpacity style={styles.modalTopBtn} onPress={onSavePress}>
              <Text style={styles.modalTopBtnText}>
                {updateId === 0 ? 'Save' : 'Update'}
              </Text>
            </TouchableOpacity>
          </View>
          <View style={styles.modalInnerContainer}>
            <DatePicker
              date={new Date(date)}
              onDateChange={date => setDate(date.getTime())}
              mode="time"
              style={styles.datePickerStyles}
              is24hourSource="locale"
            />
            <View style={styles.bottomContainerMain}>
              <View style={styles.modalBottomContainerItem}>
                <Text style={styles.modalBottomContainerLabel}>Repeat</Text>
                <TouchableOpacity style={styles.neverBtn}>
                  <Text style={{fontSize: 18}}>Never</Text>
                  <AntDesign name="right" size={20} />
                </TouchableOpacity>
              </View>
              <View style={styles.modalBottomContainerItem}>
                <Text style={styles.modalBottomContainerLabel}>Label</Text>
                <TextInput
                  placeholder="Hello there"
                  value={alarmLabel}
                  onChangeText={val => setAlarmLabel(val)}
                />
              </View>
              <View style={styles.modalBottomContainerItem}>
                <Text style={[styles.modalBottomContainerLabel, {flex: 2}]}>
                  Sound
                </Text>
                <Dropdown
                  labelField={'label'}
                  data={[
                    {value: 1, label: 'alarm_carpark'},
                    {value: 2, label: 'alarm_sneez'},
                    {value: 3, label: 'alarm_synth'},
                  ]}
                  valueField={'value'}
                  placeholder={channel}
                  value={channel}
                  onChange={item => setChannel(item.label)}
                  style={styles.dropDownStyles}
                />
              </View>
              <View style={styles.modalBottomContainerItem}>
                <Text style={styles.modalBottomContainerLabel}>Snooze</Text>
                <Switch
                  color="green"
                  value={snooze}
                  onValueChange={() => {
                    State.update(state => {
                      state.snooze = !state.snooze;
                    });
                  }}
                />
              </View>
            </View>
            {updateId !== 0 && (
              <TouchableOpacity
                onPress={onDeleteAlarmPress}
                style={styles.deleteAlarmBtn}>
                <Text style={styles.deleteAlarmBtnText}>Delete Alarm</Text>
              </TouchableOpacity>
            )}
          </View>
        </SafeAreaView>
      </Modal>
      <TouchableOpacity
        style={styles.logOutBtn}
        onPress={async () => {
          await AsyncStorage.removeItem('user')
            .then(() => {
              data.forEach((element: any) => db.deleteElement(element.id));
              console.log('success');
              navigation.navigate('signup');
              setData(undefined);
            })
            .catch(error => console.log(error));
        }}>
        <Text style={styles.logOutBtnText}>Log out</Text>
      </TouchableOpacity>
    </SafeAreaView>
  );
};

export default EntryScreen;

export const getAsyncVar = async (key: string) => {
  try {
    const response = await AsyncStorage.getItem(key);
    if (typeof response === 'object') {
      if (response) {
        return JSON.parse(response);
      }
    } else {
      return response;
    }
  } catch (error) {
    console.log(error);
  }
  return null;
};

export const getAllAsyncVars = async () => {
  try {
    const response = await AsyncStorage.getAllKeys();
    if (response) {
      return response;
    }
  } catch (error) {
    console.log(error);
  }
  return null;
};

Issues:

  • I'm encountering the error "Possible unhandled promise rejection: cannot read property 'id' of undefined." in the renderItem function.
  • There seems to be an issue with the asynchronous behavior of the autoSyncFirebase function.

Specific Questions:

  1. How can I handle the "Possible unhandled promise rejection" error in the renderItem function?
  2. Are there any improvements needed in the autoSyncFirebase function to ensure proper asynchronous behavior?

I appreciate any guidance or suggestions on resolving these issues. Thank you!


0

There are 0 answers