I have to make a react-native app (not using expo nor hooks) that can login into a user, read some simple info and then logout through a logout button or automatically due to inactivity.
I have no issues with the login, setting the timer, nor the logout button, however I have no idea of how to detect 'inactivity', is this posible with states? and how exactly?
General concensus seems to be to use PanResponder:
get user inactivity in react native
Check for Inactivity in a React Native App
state = {};
_lastInteraction = new Date();
_panResponder = {};
componentWillMount() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: this.handleStartShouldSetPanResponder,
onMoveShouldSetPanResponder: this.handleMoveShouldSetPanResponder,
onStartShouldSetPanResponderCapture: () => false,
onMoveShouldSetPanResponderCapture: () => false,
onPanResponderTerminationRequest: () => true,
onShouldBlockNativeResponder: () => false,
});
this._maybeStartWatchingForInactivity();
}
_maybeStartWatchingForInactivity = () => {
if (this._inactivityTimer) {
return;
}
this._inactivityTimer = setInterval(() => {
if (
new Date() - this._lastInteraction >= TIME_TO_WAIT_FOR_INACTIVITY_MS
) {
this._setIsInactive();
}
}, INACTIVITY_CHECK_INTERVAL_MS);
};
// NOTE: you almost certainly want to throttle this so it only fires
// every second or so!
_setIsActive = () => {
this._lastInteraction = new Date();
if (this.state.timeWentInactive) {
this.setState({ timeWentInactive: null });
}
this._maybeStartWatchingForInactivity();
};
_setIsInactive = () => {
this.setState({ timeWentInactive: new Date() });
clearInterval(this._inactivityTimer);
this._inactivityTimer = null;
};
render() {
return (
<View
style={styles.container}
collapsable={false}
{...this._panResponder.panHandlers}>
<Text style={styles.paragraph}>
Put your app here
{' '}
{this.state.timeWentInactive &&
`(inactive at: ${this.state.timeWentInactive})`}
</Text>
<Button
title="Here is a button for some reason"
onPress={() => alert('hi')}
/>
</View>
);
You can use import AsyncStorage from '#react-native-async-storage/async-storage';
So basically, whenever user visits the app, you can store the time in which user logged in.
like this
const storeData = async (value) => {
try {
await AsyncStorage.setItem('#last_visited', new Date().toString())
} catch (e) {
// saving error
}
}
and then when user again comes back to visit the app, you can check for the difference in that time and the time stored in Async storage.
first
const getData = async () => {
try {
const value = await AsyncStorage.getItem('#last_visited')
if(value !== null) {
if(new Date() - value > 5){
// here check if time diff is what as per you is inactive then logout user
// for example ive kept 5 hours
logout()
}
// value previously stored
}
} catch(e) {
// error reading value
}
}
Hope it helps. feel free for doubts
I'm new to react native, so I apologize for errors in the code. First: I use Android Studio, VS Code, react native(expo) and sqlite(expo), supporting only android (I haven't programmed for ios yet).
I have a serious problem in my code and I have no idea how to start. The 'useEffect', to work, you have to save the file 2 times (ctrl+s in vs code). Don't just open the app.
THIS IS MY LOGIN PAGE login.js
import React, {useState,useEffect} from 'react';
import {View,Text,Pressable,Image,FlatList,ImageBackground,Alert} from 'react-native';
import {TextInput} from 'react-native-gesture-handler';
import fundoLogin from './styleLogin';
import Tb_user from '../../src/models/tb_user';
import Tb_veic_user from '../../src/models/tb_veic_user';
import db from "../../src/services/SQLiteDatabse";
import NetInfo from '#react-native-community/netinfo';
const printUser = (user) => {
console.log(`ID_user:${user.ID_user}, username:${user.username}, senha:${user.senha}, ID_empresa:${user.ID_empresa}, nome_empresa:${user.nome_empresa}, status_user:${user.status_user}`)
}
const printV = (v) => {
console.log(`ID_veic_uso:${v.ID_veic_uso}, username:${v.username}, placa:${v.placa}, atividade:${v.atividade}, data_inic:${v.data_inic}, km_inic:${v.km_inic}, data_final:${v.data_final}, km_final:${v.km_final}, obs:${v.obs}, integrado:${v.integrado}`)
}
function Login({ navigation, route }) {
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const [errorLogin, setErrorLogin] = useState("")
const [users, setUsers] = useState([])
const [net, setNet] = useState("")
const [dadosVeic, setDadosVeic] = useState([])
const [info, setInfo] = useState([])
const findTbVeic = (id) => {
return new Promise((resolve, reject) => {
db.transaction((tx) => {
tx.executeSql(
"SELECT * FROM tb_veic_user WHERE integrado=0 AND km_final IS NOT NULL ;",
[],
//-----------------------
(_, { rows }) => {
if (rows.length > 0) {
resolve(rows._array)
}
else {
reject("Obj not found: id=" + id);
} // nenhum registro encontrado
},
(_, error) => reject(error) // erro interno em tx.executeSql
);
});
});
};
useEffect(() => {
NetInfo.fetch().then(state => {
setNet(state.isConnected)
});
if (net === true) {
findTbVeic()
.then(a => setDadosVeic(a))
.then( dadosVeic.map(item => {
// ENVIA DADOS PARA A API
fetch('url API', {
method: 'POST',
body: JSON.stringify({
username: item.username,
placa: item.placa,
atividade: item.atividade,
data_inic: item.data_inic,
km_inic: item.km_inic,
data_final: item.data_final,
km_final: item.km_final
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
})
//ALTERA O 'INTEGRADO' == 1
Tb_veic_user.updateV( item.ID_veic_uso )
}))
}else{
console.log('sem conexao com a internet')
}
},[])
const importTbUser = () => {
// DADOS DA API SALVA EM CONST USERS
fetch('url API')
.then( res => res.json())
.then(
(dado) => {
setUsers(dado.data)
}
)
// INSERE, NO BANCO DE DADOS, OS DADOS DA API
const a = users.map((element) => {
Tb_user.create({ID_user:element.ID_user, username:element.username, senha:element.senha, ID_empresa:element.ID_empresa, nome_empresa:element.nome_empresa, status_user:element.status_user})
});
}
const login = () => {
// FAZER LOGIN
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(
'SELECT ID_user, username, senha FROM tb_user WHERE username = ? and senha = ? LIMIT 1;',
[username, password],
//-----------------------
(_, { rows }) => {
if (rows.length > 0){
resolve(rows._array),
navigation.navigate('Veic', {
paramKey: username,
})
}else{
reject("Obj not found");
} // nenhum registro encontrado
},
(_, error) => reject(error) // Success
);
});
});
}
return (
<View style={fundoLogin.container}>
<ImageBackground
source={require('../../imagens/1.jpg')}
style={fundoLogin.imageFundo}
>
<Image
style={fundoLogin.imageLogo}
source={require('../../imagens/logo_cymi.png')}
/>
{net === true ?
<Image
style={{height:30,width:30}}
source={require('../../imagens/signal.png')}
/>
:
<Image
style={{height:30,width:30}}
source={require('../../imagens/no-signal.png')}
/>
}
<TextInput
style={fundoLogin.input}
placeholder='Digite seu email'
onChangeText={txtUsername => setUsername(txtUsername)}
value={username}
/>
<TextInput
style={fundoLogin.input}
placeholder='Digite sua senha'
secureTextEntry={true}
onChangeText={txtPassword => setPassword(txtPassword)}
value={password}
/>
{errorLogin === true
?
<View>
<Text style={fundoLogin.error}>email ou senha inválido</Text>
</View>
:
<View>
</View>
}
{(username === "" || password === "")
?
<Pressable
disabled={true}
style={fundoLogin.button}
>
<Text style={fundoLogin.textButton}>Login</Text>
</Pressable>
:
<Pressable
style={fundoLogin.button}
onPress={login}
>
<Text style={fundoLogin.textButton}>Login</Text>
</Pressable>
}
<Pressable
style={fundoLogin.button}
onPress={importTbUser}
>
<Text style={fundoLogin.textButton}>Importar</Text>
</Pressable>
<Pressable
style={fundoLogin.info}
onPress={navigation.navigate('Info')}
>
<Text style={fundoLogin.textInfo}>?</Text>
</Pressable>
</ImageBackground>
</View>
);
}
export default Login;
the app, when opened, will search (if it has internet) in the database for vehicles that have a final kilometer (which have already been selected and released), and will add this information inside the api, updating the database (changing the ' integrated to 1', that is, informing that aql "file" has already been to api)
My app works like this: it starts on the login page (where there is a login button, import button and a company information button) and goes to another page to select a vehicle (this 'veic' page also has the same problem in useEffect). All components are working perfectly fine, I've checked all possible console.logs, and it's running fine. The only problem is that use effect doesn't work (on this page it has to be run 2x to refresh, on the 'veic' page it only does one thing, and the others don't). If anyone can tell me more, please. I don't even know if it's an error in my code or in Android Studio. Thank you in advance!!!
The problem is that you are not awaiting NetInfo.fetch().
Write the code under NetInfo.fetch() in NetInfo.fetch().then(... your remaining useEffect code...).
Like this:
useEffect(() => {
NetInfo.fetch().then((state) => {
if (state.isConnected) {
findTbVeic()
.then(a => setDadosVeic(a))
.then( dadosVeic.map(item => {
// ENVIA DADOS PARA A API
fetch('url API', {
method: 'POST',
body: JSON.stringify({
username: item.username,
placa: item.placa,
atividade: item.atividade,
data_inic: item.data_inic,
km_inic: item.km_inic,
data_final: item.data_final,
km_final: item.km_final
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
})
//ALTERA O 'INTEGRADO' == 1
Tb_veic_user.updateV( item.ID_veic_uso )
}))
}else{
console.log('sem conexao com a internet')
}
});
}, []);
For your other useEffect: The problem is that you can't change a state and than use that state in an useEffect drectly after. This is cause of the execution of the useEffect. It will only rerender when variables in the useEffect array change. For example:
const Test = () => {
const [test, setTest] = useState(false);
useEffect(() => {
fetchTest().then((data) => {
// lets say data.test is "true"
setTest(data.test); // set to true
// states in useEffect will keep the value they had when the useEffect started
// DONT:
console.log("test:", test): // --> "test: false"
// DO
console.log("test:", data.test): // --> "test: true"
});
}, []);
}
I understood what Maximilan said, and on that login page it worked really well. But on the 'veic' page I'm not so lucky
useEffect(() => {
findByUserna( route.params.paramKey )
.then(
findByV(route.params.paramKey)
.then(
d => setDados(d)
),
console.log('use effect 1 ------>',dados)
)
.then(
dados.map(
item => setIDVeic(item.ID_veic_uso)
),
console.log('use effect 2 ------>',IDVeic)
)
},[])
When opening the screen, the consoles are empty, the variable is not saved, but when updating, it works, the variables are saved and shown in the console normally:
//The first time, console:
use effect 1 ------> Array []
use effect 2 ------> null
ENCONTRADO de tb_user o ID_user, username, nome_empresa ---> SORAIA
ENCONTRADO de tb_veic_user o ID_veic_uso onde kmf=null
//The second time, console:
use effect 1 ------> Array [
Object {
"ID_veic_uso": 4,
},
Object {
"ID_veic_uso": 7,
},
]
use effect 2 ------> 7
I'm trying to add a feature to my app which is add your contacts to your profile. I'm using a package for this but it works slow (in my case 470 contact record I got in my phone).
The package
import Contacts from 'react-native-contacts';
My code
componentDidMount() {
this.getContacts();
}
getContacts() {
PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_CONTACTS, {
title: 'Contacts',
message: 'This app would like to view your contacts.',
buttonPositive: 'Please accept bare mortal',
})
.then(
Contacts.getAllWithoutPhotos().then(contacts => {
var contactss = [];
contacts.map(contact => {
/// I'm mapping it for showing them in simple list with checkbox
contactss.push({...contact, checked: false});
});
// then sorting for alphabetical order by displayName
contactss.sort(function (a, b) {
if (a.displayName.toLowerCase() < b.displayName.toLowerCase())
return -1;
if (a.displayName.toLowerCase() > b.displayName.toLowerCase())
return 1;
return 0;
});
this.setState(
{contacts: contactss, loaded: true},
() => {
console.log('contacts', this.state.contacts);
},
);
}),
)
.then(contacts => {});
}
That's all code. Is this normal or should I do what?
Thank you.
I'm just trying to handle with this data. Also I'm giving select option to user which account you want, like this.
//function to select contact
checkContc(contc) {
console.log('checkFriend', contc);
let contactsTemp = this.state.contactsTemp.map(el =>
el.recordID == contc.recordID
? {...el, checked: !el.checked}
: {...el},
);
this.setState({contactsTemp}, () => {
console.log('check frined stateD ', this.state);
});
}
// render in scrollview
<ScrollView>
{this.state.contactsTemp?.map((follower, index) => {
return (
<TouchableOpacity
onPress={() => {
this.checkFriend(follower);
}}>
<FriendsComponent
checked={follower.checked}
nameSurname={follower.displayName}
key={index}
/>
</TouchableOpacity>
);
})}
</ScrollView>
result
I made a research and it looks like it is that slow. I'm not sure if the only solution isn't the native one.
I have used indicative validator to validate the form. The Validation works perfectly fine. However, the navigation stops working when its written inside try when all validation operations are performed.
state ={
name:'',
email:'',
password:'',
password_confirmation:'',
error:{},
userAllData:'',
userData:''
}
Have used async and provided the rules for the email and password, the datatype and format.
registerUser = async (data) => {
const rules = {
email:'required|email',
password:'required|string|min:5'
}
Error Messages to be shown and axios API call.
const messages = {
required: (field) => `${field} is required.`,
'email.email': ' The email is required. ',
'password.min': 'Password is too short.',
}
try {
await validateAll(data, rules, messages)
const response = await axios.get('https://react-blog-
api.bahdcasts.com/api/auth/login', {
email:data.email,
password:data.password
})
this.setState({
userData:response.data.data.user,
userAllData:response.data.data
})
NavigationService.navigate('PublicHome')
}
catch(errors){
const formattedErrors = {}
if(errors.response && errors.response.status === 422){
formattedErrors['email'] = errors.response.data['email'][0]
this.setState({ error:formattedErrors, })
}
else{
errors.forEach(error => formattedErrors[error.field] = error.message)
this.setState({ error:formattedErrors })
}
}
}
Inside render: I have the textfields of email and password with a button inside which I am calling this.registerUser(this.state) on Press handler.
<Button style={Styles.btn}
onPress={() => { this.registerUser(this.state) }}>
<Text style={Styles.loginBtnText}>{'Login'.toUpperCase()}</Text>
</Button>
I have a simple login screen that asks for an email and password.
Login Screen
If the "Sign In" button is pressed and both of the fields are blank I get this error: "null is not an object (evaluating'_this.state.Email')"
Error Screen
Here is the code:
import React, {Component} from 'react';
import {View, Button, ScrollView, AsyncStorage, Alert } from 'react-native';
import colors from '../config/colors';
import { TextInput } from '../components/TextInput';
class SignIn extends Component {
signIn = () => {
const {Email} = this.state;
const {Password} = this.state;
fetch('http://192.168.1.3/Restaurant_App/php/sign_in.php', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application.json',
},
body: JSON.stringify({
email: Email,
password: Password,
})
}).then((response) => response.json())
.then((responseJson) => {
if (responseJson == Email) {
Alert.alert(responseJson);
AsyncStorage.setItem('email', Email);
this.props.navigation.navigate('Search');
} else {
Alert.alert(responseJson);
}
}).catch((error) => {
console.error(error);
});
};
render() {
return (
<View>
<ScrollView style={{ backgroundColor: colors.background }}>
<TextInput
placeholder="Email..."
onChangeText={Email => this.setState({Email})}
/>
<TextInput
placeholder="Password..."
secureTextEntry={true}
onChangeText={Password => this.setState({Password})}
/>
</ScrollView>
<Button
onPress={() => this.signIn()}
title="Sign In"
/>
</View>
);
}
}
export default SignIn;
I would like it to be so that if the "Sign In" button is pressed with empty fields, I won't get this error. Instead, there should be an alert saying "Please fill in all fields." or something like that.
You should do some validation checks before making the fetch request.
You could do something like this
signIn = () => {
const {Email, Password} = this.state;
if(!this.checkDetails(Email, Password) {
// you could show an alert here, but it is not great UX,
// you should show your user where they have gone wrong,
// by making style changes, a red border around the TextInput,
// text explaining what has gone wrong.
return;
}
fetch('http://192.168.1.3/Restaurant_App/php/sign_in.php', {
...
}).then((response) => response.json())
.then((responseJson) => {
...
}).catch((error) => {
console.error(error);
});
};
checkDetails = (Email, Password) => {
// check that it is a valid email address
// this is a regex that I have used in the past to check email addresses.
const emailIsValid = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(Email);
// if the password has a minimum length then you should use that rather than 0
this.setState({emailIsValid, passwordIsValid: Password.length > 0});
if (emailIsValid && Password.length > 0) return true;
return false;
}
Using these new state values for the email and password being valid you could set additional styles and error text beside the fields that are wrong or missing.
<TextInput
placeholder="Email..."
onChangeText={Email => this.setState({Email})}
styles={this.state.emailIsValid ? styles.validEmail : styles.invalidEmail}
/>
{!this.state.emailIsValid && <Text>Please input a valid email</Text>}
<TextInput
placeholder="Password..."
secureTextEntry={true}
onChangeText={Password => this.setState({Password})}
styles={this.state.passwordIsValid ? styles.validPassword : styles.invalidPassword}
/>
{!this.state.passwordIsValid && <Text>Please input a valid password</Text>}
Don't for get to set up your styles for the different states.
const styles = StyleSheet.create({
validEmail: {},
validPassword: {},
invalidEmail: {},
invalidPassword: {}
});
You'll probably want to add initial state values for the emailIsValid and passwordIsValid so that they are set to true so that the correct styles are shown. Also you should define initial state for the Email and Password.
Add a constructor to your class
constructor (props) {
super(props);
this.state = {
Email: '',
Password: '',
emailIsValid: true,
passwordIsValid: true
}
}
I hope that this helps.
You can do at the top of your sign in function something like this:
If(this.state.email.length === 0 || this.state.password.length === 0) {
alert(“please complete the fields”);
return;}