Currently, it continues to run after 1min. I want it stop if the condition is met - react-native

Below, I want to clearInterval if processingStatus[0] === "DELIVERED".
useEffect(() => {
dispatch(fetchDeliveryStatus({ client_id: user.id }));
// Fetching data changes after 1 Minute
const interval = setInterval(() => {
dispatch(fetchDeliveryStatus({ client_id: user.id }));
// nextStep();
}, 60000);
if (processingStatus[0] === "DELIVERED") {
return () => clearInterval(interval);
}
}, [dispatch]);

You can use useRef(), like this
const YourComponent = () => {
const processingStatus = useSelector(...);
const processingStatusRef = useRef(null);
processingStatusRef.current = processingStatus[0];
useEffect(() => {
dispatch(fetchDeliveryStatus({ client_id: user.id }));
// Fetching data changes after 1 Minute
const interval = setInterval(() => {
if (processingStatusRef.current === "DELIVERED") {
clearInterval(interval)
} else {
dispatch(fetchDeliveryStatus({ client_id: user.id }));
}
}, 60000);
return () => clearInterval(interval);
}, [dispatch]);
}

Related

Multiple useEffect in react-native to achieve mentioned functionality

I need help with the async nature of Async storage and axios api. Here's the functionality that I am trying to achieve ->
send request to two separate api to get some data.
display that data on the screen with some additional text
api request are authenticated so a token is passed as Authentication Header
I have attached the current implementation, I am having the a number of errors in this
Errors:
Login_token not set in state after fetching from Async Storage.
Data not set in state after api call
both resulting in either failed api calls or undefined state errors on render
This is my code.
import React, { FunctionComponent, useEffect, useCallback, useState} from 'react';
import { StyleSheet, View} from 'react-native';
// chat
import { GiftedChat } from 'react-native-gifted-chat';
// navigation
import { RootStackParamList } from '../../navigators/RootStack';
import { StackScreenProps } from '#react-navigation/stack';
export type Props = StackScreenProps<RootStackParamList, "Chat">;
// api
import { Convo_details, Send_Msg, List_Msg, Expert_Public_Profile } from '../../api/UserApi';
import Spinner from 'react-native-loading-spinner-overlay';
import AsyncStorage from '#react-native-async-storage/async-storage';
import uuid from 'react-native-uuid';
const Chat: FunctionComponent<Props> = ({ navigation, route, ...props }) => {
// console.log(props.route.params);
const [login_token, setlogin_token] = useState('')
const [conversation_id, setconversation_id] = useState('')
const [conversation_details, setconversation_details] = useState({})
const [currentuser, setcurrentuser] = useState({})
const [loading, setLoading] = useState(false);
const [expertuid, setexpertuid] = useState('')
const [ExpertProfile, setExpertProfile] = useState({})
const [messages, setMessages] = useState([]);
useEffect(() => {
getlogintoken()
console.log("####################################","getlogintoken");
}, [])
/* conversationid */
useEffect(() => {
if (route.params != null) {
setconversation_id(route.params[0])
}
console.log("####################################","conversation id");
}, [])
/* expert uid */
useEffect(() => {
if (route.params != null) {
setexpertuid(route.params[1])
}
console.log("####################################","expert uid");
}, [])
/* expert public profile */
useEffect(() => {
getexpertpublicprofile()
getConvo_details()
console.log("####################################","convo_details");
}, [])
useEffect(() => {
// get current user
AsyncStorage.getItem("currentuser").then(res => {
if (res != null) setcurrentuser(res)
else alert("Current user not found")
})
console.log("####################################","current user");
}, [])
// set welcome msg
useEffect(() => {
if (Object.keys(conversation_details).length != 0 && Object.keys(ExpertProfile).length != 0)
setwelcomemsg()
}, [])
const onSend = useCallback(async (messages = []) => {
// console.log(messages[0].text);
setMessages(previousMessages => GiftedChat.append(previousMessages, messages))
const data = {
conversation_id: "f98d6851-a713-4f58-9118-77a779ff175f",//conversation_id,
message_type: "TEXT",
body: messages[0].text
}
const res: any = await Send_Msg(data, login_token)
.catch(error => {
alert(`Send_Msg -> ${error}`)
console.log(error);
return
})
if (res.status == 200) {
console.log(res.data);
} else console.log(res);
}, [])
const getexpertpublicprofile = async () => {
setLoading(true)
const res: any = await Expert_Public_Profile(expertuid, login_token)
.catch(error => {
setLoading(false)
console.log("Expert public profile ->");
alert(`Expert public profile ->${error.message}`)
console.log(error);
return
})
setLoading(false)
if (res.status === 200) setExpertProfile(res.data)
else {
alert(`get expert public profile${res.data.message}`)
console.log("getexpertpublicprofile -->");
console.log(res.data);
}
}
const getlogintoken = () => {
AsyncStorage.getItem("login_token").then(res => {
if (res != null) {
setLoading(false)
setlogin_token(res)
}
else alert("No login token found")
})
}
const getConvo_details = async () => {
setLoading(true)
const res: any = await Convo_details(conversation_id, login_token)
.catch(error => {
setLoading(false)
alert(`Convo_details-->${error.message}`)
console.log("Convo_details -->");
console.log(error);
return
})
setLoading(false)
if (res.status === 200) setconversation_details(res.data)
else {
alert(`get convo details-> ${res.data.message}`)
console.log("getConvo_details -->");
console.log(res.data);
}
}
const setwelcomemsg = () => {
try {
let user = JSON.parse(currentuser)
let messages = [
{
_id: uuid.v4().toString(),
conversation_id: conversation_details.conversation_id,
created_at: new Date(),
from: conversation_details.recipient.user_uid,
type: "TEXT",
text: `About Me - ${ExpertProfile.bio}`,
user: {
_id: conversation_details.recipient.user_uid,
}
},
{
_id: uuid.v4().toString(),
conversation_id: conversation_details.conversation_id,
created_at: new Date(),
from: conversation_details.recipient.user_uid,
type: "TEXT",
text: `My name is ${conversation_details.recipient.name}`,
user: {
_id: conversation_details.recipient.user_uid,
}
},
{
_id: uuid.v4().toString(),
conversation_id: conversation_details.conversation_id,
created_at: new Date(),
from: conversation_details.recipient.user_uid,
type: "TEXT",
text: `Hi ${user.full_name}`,
user: {
_id: conversation_details.recipient.user_uid,
}
}]
setMessages(previousMessages => GiftedChat.append(previousMessages, messages))
} catch (error) {
console.log("try -> set welcome msg");
console.log(error);
return
}
}
return (
<View style={styles.maincontainer}>
<Spinner
visible={loading}
textContent={'Loading...'}
textStyle={{ color: '#FFF' }}
/>
<GiftedChat
messages={messages}
onSend={messages => onSend(messages)}
user={{
_id: currentuser.user_uid,
}}
isTyping={false}
scrollToBottom={true}
showAvatarForEveryMessage={true}
renderAvatar={() => null}
/>
</View>
);
}
export default Chat;
const styles = StyleSheet.create({
maincontainer: {
flex: 1,
},
});
When axios returns, it usually give the response as res.data, so in your case, try either res.data or res.data.yourToken (I'm not sure how it's your object).
Gurav,
As far as your code above, The api call's will trigger even before you get currentuser or loginToken. You have to handle the api call after getting the currentuser and loginToken. This can be gracefully handled with async, await.
example code:
useEffect(() => {
getData()
}, [])
useEffect(() => {
if(login_token && currentuser) {
//The api call goes here after you get the logintoken andcurrentuser.
// The above condition is just an example but will vary based on your requirements
}
}, [login_token, currentuser])
const getData = async () => {
await getlogintoken()
await getcurrentuser()
}
const getlogintoken = async () => {
await AsyncStorage.getItem("login_token").then(res => {
if (res != null) {
setLoading(false)
setlogin_token(res)
}
else alert("No login token found")
})
}
const getcurrentuser = async () => {
await AsyncStorage.getItem("currentuser").then(res => {
if (res != null) setcurrentuser(res)
else alert("Current user not found")
})
}

Close React Native apps / dispatch redux action when running on background

I want to create a function inside my components that detect when the app is in the background for 30 seconds, I want to dispatch a logout action or close the apps. is that possible if we do that in react native?
I'm using hooks
Thanks,
update :
I'm using the wowandy's solution but the thing is if user close the apps for less than 10 seconds and then open the app again, the dispatch command will still be executed in 30 seconds. is there any way to cancel the timeout ?
useEffect(() => {
let timeout;
const subscription = AppState.addEventListener('change', (nextAppState) => {
clearTimeout(timeout);
if (appState.current === 'background') {
timeout = setTimeout(() => dispatch(removeDataLogin()), 30000);
}
appState.current = nextAppState;
});
return () => {
subscription.remove();
clearTimeout(timeout);
};
}, []);
Update 3
So I tried to use Michael Bahl's solution as commented below. it works great with timestamp.
useEffect(() => {
let start;
let end;
const subscription = AppState.addEventListener("change", nextAppState => {
if (
appState.current.match(/inactive|background/) &&
nextAppState === "active"
) {
end = new Date()
let ms = moment(end).diff(moment(start))
if (Number(ms) >= 30000) {
dispatch(removeDataLogin())
} else {
}
} else {
start = new Date()
console.log('start diff :', start)
}
appState.current = nextAppState;
setAppStateVisible(appState.current);
console.log("AppState", appState.current);
});
return () => {
subscription.remove();
};
}, []);
update 3 Im using Michael Bahl's solution so I created a timestamp that check the difference between inactive and active screens, then dispatch the redux action
useEffect(() => {
let start;
let end;
const subscription = AppState.addEventListener("change", nextAppState => {
if (
appState.current.match(/inactive|background/) &&
nextAppState === "active"
) {
console.log('end =====')
console.log('start diff == ', start)
end = new Date()
console.log('end diff ===', end)
let ms = moment(end).diff(moment(start))
console.log('different : ', ms)
console.log(typeof ms)
if (Number(ms) >= 30000) {
console.log('harusnya logout')
dispatch(removeDataLogin())
} else {
console.log(ms, 'masuk sini')
}
} else {
start = new Date()
console.log('start diff :', start)
}
appState.current = nextAppState;
setAppStateVisible(appState.current);
console.log("AppState", appState.current);
});
return () => {
subscription.remove();
};
}, []);
You can handle app state using AppState and close it with BackHandler
See example:
import React, {useRef, useEffect} from 'react';
import {AppState, BackHandler} from 'react-native';
const App = () => {
const appState = useRef(AppState.currentState);
useEffect(() => {
let timeout;
const subscription = AppState.addEventListener('change', (nextAppState) => {
clearTimeout(timeout);
if (appState.current === 'background') {
timeout = setTimeout(() => BackHandler.exitApp(), 30000);
}
appState.current = nextAppState;
});
return () => {
subscription.remove();
clearTimeout(timeout);
};
}, []);
// TODO
return /* TODO */;
};

Adding appointment to calendar using Expo createEventAsync IOS

SDK Version: 38
Platforms(Android/iOS):
I am having difficulties getting this code to return a calendar ID, I’d appreciate help from anyone as there seems to be very little information about the new calendar API change.
async obtainCalendarPermission() {
let permission = await Permissions.getAsync(Permissions.CALENDAR)
if (permission.status !== 'granted') {
permission = await Permissions.askAsync(Permissions.CALENDAR)
return
}
if (permission.status !== 'granted') {
permission = await Permissions.askAsync(Permissions.REMINDERS)
return
if (permission.status !== 'granted') {
Alert.alert('Permission not granted to calendar')
}
}
return permission
}
async function getDefaultCalendarSource() {
const calendars = await Calendar.getCalendarsAsync()
const defaultCalendars = calendars.filter(
(each) => each.source.name === 'Default',
)
return defaultCalendars[0].source
}
async addReservationToCalendar(date){
await this.obtainCalendarPermission()
const permission = await Permissions.askAsync(Permissions.REMINDERS)
if (permission.status !== 'granted')
var calendars = Calendar.getCalendarsAsync().then(calendars => console.log(calendars))
const defaultCalendarSource = Platform.OS === 'ios' ? await getDefaultCalendarSource(): { isLocalAccount: true, name: 'Expo Calendar' };
console.log(defaultCalendarSource ,+'emeka')
let dateMs = Date.parse(date)
let startDate = new Date(dateMs)
let endDate = new Date(dateMs + 2 * 60 * 60 * 1000)
const calendarId = await Calendar.createEventAsync(
defaultCalendarSource.id,
{
title: 'Con Fusion Table Reservation',
startDate: startDate,
endDate: endDate,
timeZone: 'Asia/Hong_Kong',
location:
'121, Clear Water Bay Road, Clear Water Bay, Kowloon, Hong Kong',
},
)}
In my case I decided to implement EVENTS for android and REMINDERS for iOS. I show you my solution, but it's a bit different. It works perfectly on both platforms.
I'm using:
"expo": "46.0.13"
"expo-calendar": "10.3.0"
"react-native": "0.69.6"
useEffect(() => {
(async () => {
if (Platform.OS === 'ios') {
await requestTrackingPermissionsAsync();
const remindersPermissions = await Calendar.requestRemindersPermissionsAsync();
if(remindersPermissions.status !== 'granted')
Alert.alert('Reminders access required', 'You must accept access to reminders in order to create reminders.',
[
{ text: "Cancel", style: "cancel" },
{ text: "OK", onPress: async () => await Calendar.requestRemindersPermissionsAsync() }
])
}
const calendarPermissions = await Calendar.requestCalendarPermissionsAsync();
if(calendarPermissions.status === 'granted') {
createCalendar()
} else {
Alert.alert('Access to calendars required', 'You must allow access to the calendar if you want to create reminders.',
[
{ text: "Cancel", style: "cancel" },
{ text: "OK", onPress: async () => await Calendar.requestCalendarPermissionsAsync() }
])
}
})();
}, [])
const getDefaultCalendarSource = async() => {
const defaultCalendar = await Calendar.getDefaultCalendarAsync();
return defaultCalendar.source;
}
const createCalendar = async () => {
const calendarEntityType = Platform.OS === 'android' ? Calendar.EntityTypes.EVENT : Calendar.EntityTypes.REMINDER;
const calendars = Platform.OS === 'android' ? await Calendar.getCalendarsAsync() : await Calendar.getCalendarsAsync(calendarEntityType);
const existCalendar = calendars.find(calendar => calendar.title === DEFAULT_CALENDAR_NAME);
if (!existCalendar) {
const defaultCalendarSource = Platform.OS === 'ios' ? await getDefaultCalendarSource() : { isLocalAccount: true, name: DEFAULT_CALENDAR_NAME };
const newCalendarId = await Calendar.createCalendarAsync({
title: DEFAULT_CALENDAR_NAME,
name: DEFAULT_CALENDAR_NAME,
color: primerColor,
entityType: calendarEntityType,
accessLevel: Calendar.CalendarAccessLevel.OWNER,
sourceId: defaultCalendarSource.id,
source: defaultCalendarSource,
ownerAccount: DEFAULT_CALENDAR_NAME,
});
globalContext.setCalendarId(newCalendarId)
} else {
const calendarIndex = calendars.findIndex(calendar => calendar.title === DEFAULT_CALENDAR_NAME);
globalContext.setCalendarId(calendars[calendarIndex].id);
}
}
And to create Events/Reminders I have this function:
const onSetReminder = async (reminderDate) => {
let reminderId;
let eventId;
Platform.OS === 'ios' && (async() => {
reminderId = await Calendar.createReminderAsync(globalContext.calendarId, {
title: `Reminder Title`,
calendarId: globalContext.calendarId,
startDate: reminderDate,
dueDate: reminderDate,
alarms: [{
relativeOffset: -60,
method: Calendar.AlarmMethod.ALERT
}],
});
})();
Platform.OS === 'android' && (async() => {
eventId = await Calendar.createEventAsync(globalContext.calendarId, {
title: `Event Title`,
accessLevel: Calendar.EventAccessLevel.OWNER,
calendarId: globalContext.calendarId,
startDate: reminderDate,
endDate: reminderDate,
alarms: [{
method: Calendar.AlarmMethod.ALERT,
relativeOffset: -60,
}],
});
})();
};
I have solved it. I was having a hard time getting the calendar ID as it is a requirement for the new createEventAsync() API. this is the complete code that solved the problem for me, I am pasting it here so that someone can use or improve on it.
It generates a unique calendar ID and saves reservations to the calendar on IOS, I did not test it on android.
async function getDefaultCalendarSource() {
await Calendar.getCalendarsAsync().then((id) => console.log(id))
}
async obtainCalendarPermission() {
let permission = await Permissions.getAsync(Permissions.CALENDAR)
if (permission.status !== 'granted') {
permission = await Permissions.askAsync(Permissions.CALENDAR)
return
}
if (permission.status !== 'granted') {
permission = await Permissions.askAsync(Permissions.REMINDERS)
return
if (permission.status !== 'granted') {
Alert.alert('Permission not granted to calendar')
}
}
return permission
}
async addReservationToCalendar(date) {
await this.obtainCalendarPermission()
var dateMs = Date.parse(date)
var startDate = new Date(dateMs)
var endDate = new Date(dateMs + 2 * 60 * 60 * 1000)
getDefaultCalendarSource()
const newCalendar = await Calendar.createCalendarAsync({
title: 'Test Reservation',
color: '#512DA8',
entityType: Calendar.EntityTypes.EVENT,
sourceId: getDefaultCalendarSource.id,
source: getDefaultCalendarSource,
name: 'Restauran Reservation',
ownerAccount: 'personal',
accessLevel: Calendar.CalendarAccessLevel.OWNER,
})
.then((id) => {
Calendar.createEventAsync(id, {
title: 'Table Reservation',
startDate: startDate,
endDate: endDate,
timeZone: 'Asia/Hong_Kong',
location:
'121, Clear Water Bay Road, Clear Water Bay, Kowloon, Hong Kong',
}).catch((err) => console.log(err))
// console.log(`calendar ID is: ${id}`)
})
.catch((err) => console.log(err))
}

jest tests for Axios.all and Axios.spread

I am struggling to write JEST test cases for below method
getStudentList (studentList:}[]) {
if (studentList.length < 1) {
Promise.resolve()
}
let promises = []
for (const student of StudentList) {
if (!student.name) {
Promise.resolve()
}
var url = `${API_URL}/${student.name}/`
promises.push(Axios.get(url}))
}
return Axios.all(promises)
.then(Axios.spread((...args) => {
// customise the response here
return args
.map(response => response.data)
.map(data => {
//do something with data
return data
})
}))
It uses axios.all and axios.spread to get the data back..i have written simple test cases for Axios.get..but how to write test case for this? This method is in a vue project in a service class
This is a short example of how you can write your expectations (with 100% coverage) for the code above:
import myService from './myService';
import Axios from 'axios';
jest.mock('axios');
global.API_URL = 'http://example.com/mock_api';
describe('myService', () => {
describe('getStudentList', () => {
describe('without students in the list', () => {
it('should result undefined', () => {
const result = myService.getStudentList();
expect(result).resolves.toEqual( undefined );
});
});
describe('with students in the list', () => {
const mockStudentList = [{
name: 'student1',
}, {
someProp: 'some value',
}, {
name: 'student3',
}];
const results = [];
const mockAxiosSpreadResult = jest.fn();
beforeAll(() => {
Axios.get.mockClear();
Axios.all.mockResolvedValue(results);
Axios.spread.mockReturnValue(mockAxiosSpreadResult);
myService.getStudentList( mockStudentList );
});
it('should call Axios.get once for each student with name', () => {
expect(Axios.get).toHaveBeenCalledWith(`${API_URL}/student1/`);
expect(Axios.get).toHaveBeenCalledWith(`${API_URL}/student3/`);
});
it('should call Axios.spread with a callback', () => {
expect(Axios.spread).toHaveBeenCalledWith(expect.any(Function));
});
it('should call the result of Axios.spread with the resolved value of Axios.all', () => {
expect(mockAxiosSpreadResult).toHaveBeenCalledWith(results);
});
describe('Axios.spread callback', () => {
let callback;
beforeAll(() => {
callback = Axios.spread.mock.calls[0][0];
});
describe('called with parameters', () => {
let result;
beforeAll(() => {
result = callback({
data: 1
},{
data: 2
},{
data: 3
},{
data: 4
});
});
it('should do something with the data', () => {
expect(result).toEqual([1,2,3,4]);
});
});
});
});
});
});
working example

Simulating a loading spinner before debounce

does anyone know how can I execute the this.isLoading = true before the debounce in this method?
It was supposed to be a loading spinner that will be animated when making async call via axios.
methods: {
searchAdminUsers: _.debounce(function(query) {
this.isLoading = true
axios.get('api/searchEmployees?format=json', { params: { q:query } })
.then(response => {
let data = response.data.map(item => (
{ text: `${item.FIRSTNAME} ${item.LASTNAME} - ${item.POSITION}`, id: item.ID }
))
this.options = data
this.isLoading = false
})
.catch(error => {
console.log(error)
})
}, 250)
}
Create another method that changes this.isLoading, and invokes the debounces method.
methods: {
wrapSearchAdminUsers(query) {
this.isLoading = true
this.searchAdminUsers(query)
}
searchAdminUsers: _.debounce(function(query) {
axios.get('api/searchEmployees?format=json', { params: { q:query } })
.then(response => {
let data = response.data.map(item => (
{ text: `${item.FIRSTNAME} ${item.LASTNAME} - ${item.POSITION}`, id: item.ID }
))
this.options = data
this.isLoading = false
})
.catch(error => {
console.log(error)
})
}, 250)
}