react-native-setTimeout do not work well - react-native

I want to change the state by setTimeout
componentDidMount() {
this.timer = setTimeout(() => {
this.setState({
splashed: true
})
console.log('!');
}, 5000)
}
componentWillUnmount() {
console.log('nani');
this.timer && clearTimeout(this.timer);
}
but the setTimeout run soon when trigger componentDidMount , do not wait 5 seconds. how can i do it?
Thanks for your any suggestions.

Try to use another function for setting the state:
componentDidMount = () => {
this.timer = setTimeout(() => {
this.setSplashed();
console.log('!');
}, 5000)
}
setSplashed = () => {
this.setState({
splashed: true
})
}

Related

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 */;
};

How to write a JEST test for a useEffect with timers?

I am using #react-native-community/netinfo to detect the app connectivity state and showing a message when connection is lost. How do I write a test for the following code that's in a useEffect to make sure that the message is showing/hiding and the cleanup works?
const { isConnected } = useContext(ConnectionContext);
...
useEffect(() => {
const snack = setTimeout(() => {
if (!isConnected) {
showMessage({
autoHide: false,
message: 'Please try again later',
});
}
}, 10000);
const hideSnack = setTimeout(() => {
if (isConnected) hideMessage();
}, 5000);
return () => {
clearTimeout(snack);
clearTimeout(hideSnack);
};
}, [isConnected]);
I have tried something like this to check if the app is connected
jest.mock('#react-native-community/netinfo', () => ({
...jest.requireActual('#react-native-community/netinfo'),
useNetInfo: () => ({
isConnected: true,
})
}));
You can use jest fake timers to control the time:
at the top use
jest.useFakeTimers();
then when you need to advance time by certain amount use :
jest.advanceTimersByTime(100);

exit app after second back press using toast

This exitAlert is call after android button is press, I want to disable exit after the toast is close, since the toast has no close event, I am using timeout to disable it, obviously the code below does not disable second press exit:
const exitAlert = () => {
const duration = 3 * 1000;
showToast('Press again to exit', duration, 'top');
BackHandler.addEventListener('hardwareBackPress', () => {
BackHandler.exitApp();
});
setTimeout(() => BackHandler.removeEventListener('hardwareBackPress', () => {}),
duration);
}
Okay this works:
let pressTwice = true;
const duration = 3 * 1000;
showToast('Confirm exit', duration, 'top');
BackHandler.addEventListener('hardwareBackPress', () => {
if (pressTwice) {
BackHandler.exitApp();
}
});
setTimeout(function() {
pressTwice = false;
}, duration);
This may work
componentDidMount(){
let oncePressed = false;
const duration = 3 * 1000;
BackHandler.addEventListener('hardwareBackPress', () => {
if(oncePressed){
oncePressed = false;
BackHandler.exitApp();
}else{
showToast('Press again to exit', duration, 'top');
oncePressed = true
setTimeout(() => BackHandler.removeEventListener('hardwareBackPress', () => {
oncePressed = true
}),
duration);
}
});
}

react native async getting data when running app first time

I have two components, in first components storing data in asyncstorage, in second component display data, when install app and save data does not get data from asyncstorage, when open app second time data are displayed.
storeData = async (item, messave, messrem) => {
const checkarary = this.state.favorite;
if(checkarary.some(e => e.name === item.name)) {
const value = this.state.favorite;
const position = value.filter((lists) => lists.id !== item.id);
this.setState({
favorite: position
}, () => {
try {
AsyncStorage.setItem('favoriti', JSON.stringify(this.state.favorite), () => {
Toast.show({
text: messrem,
buttonText: "Okay",
duration: 3000,
type: "danger"
});
});
} catch (error) {
}
});
} else {
this.setState({
favorite: [...this.state.favorite, item]
}, () => {
try {
AsyncStorage.setItem('favoriti', JSON.stringify(this.state.favorite), () => {
// AsyncStorage.getItem('favoriti', (err, result) => {
// console.log(result);
// });
Toast.show({
text: messave,
buttonText: "Okay",
duration: 3000,
type: "success"
});
});
} catch (error) {
}
});
}
};
Getting data in second component
_retrieveData = async () => {
try {
AsyncStorage.getItem('favoriti').then((value) => {
const parsed = JSON.parse(value);
this.setState({ favorite: parsed })
})
} catch (error) {
}
};
componentDidMount() {
this._retrieveData();
setTimeout(() => {
this.setState({
loading: false,
})
}, 2000)
};
componentDidUpdate() {
this._retrieveData();
};
How fix this issue, is there some solution. Can I set Item and reload app when install app or somthing else.
Use this
componentWillMount() {
this._retrieveData();
setTimeout(() => {
this.setState({
loading: false,
})
}, 2000)
};
instead of
componentDidMount() {
this._retrieveData();
setTimeout(() => {
this.setState({
loading: false,
})
}, 2000)
};
As componentWillMount is called after constructor is called for class and componentDidMount is called after screen is once rendered.

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)
}