Can't understand how to use React Native useEffect - react-native

I have a login view in a React Native application:
import React, {useState, useContext, useEffect} from 'react';
import {View, StyleSheet} from 'react-native';
import {Button, TextInput, Headline} from 'react-native-paper';
import globalStyles from '../styles/global';
import axios from 'axios';
import AsyncStorage from '#react-native-community/async-storage';
import AuthContext from '../context/auth/authContext';
const Login = ({navigation, route}) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const {user, setUser} = useContext(AuthContext);
const setLocalStorageUser = async (user) => {
try {
await AsyncStorage.setItem('user', user);
} catch (error) {
console.log(error);
}
};
const handleNewUserPress = () => {
navigation.navigate('Signup');
}
const handleLoginPress = async () => {
try {
const loginData = {
username: email,
password: password,
}
responseData = await axios.post(loginURL, loginData);
setLocalStorageUser('user', {email: email, token: responseData.token});
useEffect(() => {
setUser({email: email, token: responseData.token});
}, []);
} catch (error) {
console.log(error);
}
navigation.navigate('Home');
}
return (
<View style={globalStyles.container}>
<TextInput style={styles.input} value={email} label="Email" onChangeText={(text) => setEmail(text)} />
<TextInput style={styles.input} value={password} label="ContraseƱa" onChangeText={(text) => setPassword(text)} />
<Button
style={styles.button}
mode='contained'
onPress={() => handleLoginPress()}
disabled={email=='' || password==''}
>
Enviar
</Button>
<Button icon="plus-circle" onPress={() => handleNewUserPress()}>
Nuevo Usuario
</Button>
</View>
);
}
const styles = StyleSheet.create({
input: {
marginBottom: 20,
backgroundColor: 'transparent'
},
button: {
marginBottom: 20
}
})
export default Login;
The problem is in function handleLoginPress()when calls useEffect(). I get this error:
[Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See url for tips about how to debug and fix this problem.]
I have no idea why it happens and how to solve it.

First, you need to understand, what does the useEffect hook does. According to the documentation:
The Effect Hook lets you perform side effects in function components
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
It was created because we didn't have a way to manage the state inside functional components. We needed to convert the component into a class and use lifecycle methods like: componentDidMount or componentDidUpdate.
In your case, you don't need to use the useEffect hook since your action is being executed when you click the button to login.
You'd like to be using useEffect when:
You need to fetch data
You need to check if the user is logged in
etc...

useEffect is called as a function in the main function before return your jsx, but not inside of the other function or function arrow that you are declaring in the main function. In your case :
import {Button, TextInput, Headline} from 'react-native-paper';
import globalStyles from '../styles/global';
import axios from 'axios';
import AsyncStorage from '#react-native-community/async-storage';
import AuthContext from '../context/auth/authContext';
const Login = ({navigation, route}) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const {user, setUser} = useContext(AuthContext);
useEffect(() => {
setUser({email: email, token: responseData.token});
}, []);
const setLocalStorageUser = async (user) => {
try {
await AsyncStorage.setItem('user', user);
} catch (error) {
console.log(error);
}
};
const handleNewUserPress = () => {
navigation.navigate('Signup');
}
const handleLoginPress = async () => {
try {
const loginData = {
username: email,
password: password,
}
responseData = await axios.post(loginURL, loginData);
setLocalStorageUser('user', {email: email, token: responseData.token});
} catch (error) {
console.log(error);
}
navigation.navigate('Home');
}
return (
<View style={globalStyles.container}>
<TextInput style={styles.input} value={email} label="Email" onChangeText={(text) => setEmail(text)} />
<TextInput style={styles.input} value={password} label="ContraseƱa" onChangeText={(text) => setPassword(text)} />
<Button
style={styles.button}
mode='contained'
onPress={() => handleLoginPress()}
disabled={email=='' || password==''}
>
Enviar
</Button>
<Button icon="plus-circle" onPress={() => handleNewUserPress()}>
Nuevo Usuario
</Button>
</View>
);
}
const styles = StyleSheet.create({
input: {
marginBottom: 20,
backgroundColor: 'transparent'
},
button: {
marginBottom: 20
}
})
export default Login;
Regards

You don't need useEffect inside handleLoginPress:
const handleLoginPress = async () => {
try {
const loginData = {
username: email,
password: password,
}
responseData = await axios.post(loginURL, loginData);
setLocalStorageUser('user', {email: email, token: responseData.token});
setUser({email: email, token: responseData.token});
} catch (error) {
console.log(error);
}
navigation.navigate('Home');
}

As the error says: You can set useEffect in a function component only, It's not allowed useEffect inside a function expression inside a function component. The best solution, if you want to manage the useEffect you have to handle in the body of your component function and update the state to trigger it.

Related

React Native Render Error undefined is not an object (evaluating '_useContext.register')

My useContext look like is work but when i chnage screen to SignUpScreen it give me Error like this
Render Error undefined is not an object (evaluating
'_useContext.refister')
here is my SignUpScreen.js
import React, { useContext, useState } from 'react';
import { View, Text, Button, StyleSheet, TouchableOpacity, Image } from 'react-native';
import FormInput from '../components/FormInput';
import FormButton from '../components/FormButton';
import FormRedButton from '../components/FormRedButton'
import { firebase } from '../firebase/Firebase'
import { AuthContext, AuthProvider } from "../database/AuthProvider";
const SignupScreen = ({ navigation }) => {
const [email, setEmail] = useState();
const [password, setPassword] = useState();
const [confirmPassword, setConfirmPassword] = useState();
const {register} = useContext(AuthContext);
return (
<View style={styles.container}>
{/* <Image
source={require('../Logo/Mahan2.jpg')}
style={styles.logo}
/> */}
<Text style={styles.text}>Create an account</Text>
{/* UserName */}
<FormInput
lableValue={email}
onChangeText={(userEmail) => setEmail(userEmail)}
placeholderText="Email"
iconType="user"
autoCapitalize="none"
autoCorrect={false}
/>
{/* Password */}
<FormInput
lableValue={password}
onChangeText={(userPassword) => setPassword(userPassword)}
placeholderText="Password"
iconType="lock"
secureTextEntry={true}
/>
{/* Confirm Password */}
<FormInput
lableValue={confirmPassword}
onChangeText={(userConfirmPassword) => setConfirmPassword(userConfirmPassword)}
placeholderText="Confirm Password"
iconType="lock"
secureTextEntry={true}
/>
<FormButton
buttonTitle="Sign Up"
onPress={() => alert("Sing Up")}
/>
<FormRedButton
buttonTitle="Back"
onPress={() => navigation.navigate('Login')}
/>
</View>
)
}
export default SignupScreen;
const styles = StyleSheet.create({
......
})
And this is my AuthProvider.js
import React, { createContext, useState } from "react";
import auth from '#react-native-firebase/auth';
export const AuthContext = createContext();
export const AuthProvider = ({children}) => {
const [user, setUser] = useState(null);
return(
<AuthContext.Provider
value={{
user,
setUser,
login: async (email, password) => {
try{
await auth().signInWithEmailAndPassword(email, password);
} catch(e) {
console.log(e);
}
},
register: async (email, password) => {
try{
await auth().createUserWithEmailAndPassword(email, password);
} catch(e) {
console.log(e);
}
},
logout: async () => {
try{
await auth().signOut()
} catch(e) {
console.log(e);
}
}
}}
>
{children}
</AuthContext.Provider>
)
}
Here is my ScreenShot Error
enter image description here
I have no Idea how to solve it
You forgot to wrap App component , check this example :
https://snack.expo.dev/AVVpx0PJ7

I have a problem in navigation in react-native while using the Axios

I want to navigate to a new page when the email and password is correct(when click the button) .
ie while click button I i want to check API and navigate to another page ,if email and password is
correct
Here I use AXIOS for API integration.
---------This is my code----------
import React, {useState} from 'react';
import {View,Text, TextInput, Button} from 'react-native';
import axios from 'axios';
import { useNavigation } from '#react-navigation/native';
const NewsCard = ()=>{
const navigation = useNavigation();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const[name ,setName] = useState('namesssssssss')
return(
<View>
<Text>{name}</Text>
<TextInput
autoCorrect={false}
autoCapitalize="none"
keyboardType="email-address"
onChangeText={ (text) => setEmail(text)}
value={email}
/>
<TextInput
autoCorrect={false}
autoCapitalize="none"
onChangeText={(pswrd) => setPassword(pswrd)}
value={password}
/>
<Button onPress={ ()=>
axios.post('http://10.0.2.2:5000/api/admin/login',
{
email: email,
password: password,
})
.then(function (response) {
// handle success
console.log(response.data);
})
.catch(function (error) {
// handle error
alert('Wrong Email or Password');
})
.then(function () {
})
} title="Click"></Button>
</View>
)
}
export default NewsCard
Calling an API request directly from Button is highly unadvised, but this solution should work
First Create a handle function for login , calling API from render (return) is bad practice.
handleLogin
const NewsCard = ()=>{
const navigation = useNavigation();
const handleLogin = () => {
axios.post('http://10.0.2.2:5000/api/admin/login',
{
email: email,
password: password,
})
.then(function (response) {
// handle success
navigation.navigate('RouteName');
console.log(response.data);
})
.catch(function (error) {
// handle error
alert('Wrong Email or Password');
})
.then(function () {
})
call it in the Button component
<Button onPress={handleLogin} title="Click"></Button>
That way your code will look way more readable and clean
import React, {useState} from 'react';
import {View,Text, TextInput, Button} from 'react-native';
import axios from 'axios';
import { useNavigation } from '#react-navigation/native';
const NewsCard = ()=>{
const navigation = useNavigation();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const[name ,setName] = useState('namesssssssss')
return(
<View>
<Text>{name}</Text>
<TextInput
autoCorrect={false}
autoCapitalize="none"
keyboardType="email-address"
onChangeText={ (text) => setEmail(text)}
value={email}
/>
<TextInput
autoCorrect={false}
autoCapitalize="none"
onChangeText={(pswrd) => setPassword(pswrd)}
value={password}
/>
<Button onPress={ ()=>
axios.post('http://10.0.2.2:5000/api/admin/login',
{
email: email,
password: password,
})
.then(function (response) {
// handle success
navigation.navigate('SomeComponent');
console.log(response.data);
})
.catch(function (error) {
// handle error
alert('Wrong Email or Password');
})
.then(function () {
})
} title="Click"></Button>
</View>
)
}
export default NewsCard
react-native
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Above code can fix your problem but it isn't a right way to do this.
To do this right, you should do this in Navigator
import * as React from "react";
import { NavigationContainer } from "#react-navigation/native";
import { useSelector } from "react-redux";
import HomeStack from "./HomeStack"; //stack navigator which holds your actual app
import AuthStack from "./AuthStack"; //stack navigator which ables a user to login to your app
export const Navigator = () => {
// some state that you may have declared in your reducers
const auth = useSelector((state) => state.auth);
return (
<NavigationContainer>
{auth.isLoggedIn ? <HomeStack /> : <AuthStack />}
</NavigationContainer>
);
};
You can read more about authentication flows here

Change screen without a click event using navigation stack react native

Well what I'm trying to do is when he finishes reading the qr code is to move to the next screen as soon as this event ends. I tried to do this by declaring:
const handleBarCodeScanned = ({ type, data }) => {
{this.props.navigation.navigate ('testScreen', {data1, data2})}
}
Usually, the documentation always shows accompanied by an onClick () function associated with a button.
import React, { useState, useEffect } from 'react';
import { Text, View, StyleSheet, Button, PermissionsAndroid } from 'react-native';
import { BarCodeScanner } from 'expo-barcode-scanner';
import wifi from 'react-native-android-wifi';
export default function QrCodeScreen() {
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
useEffect(() => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === 'granted');
})();
}, []);
const handleBarCodeScanned = ({ type, data }) => {
{this.props.navigation.navigate('nextScreen', { data1, data2 })}//Change screen
})}
};
if (hasPermission === null) {
return <Text>Requesting for camera permission</Text>;
}
if (hasPermission === false) {
return <Text>No access to camera</Text>;
}
return (
<View
style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-end',
}}>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
/>
{scanned && <Button title={'Tap to Scan Again'} onPress={() => setScanned(false)} />}
</View>
);
}
Seems like you're using functional components so there is no this context.
You forget to import and init the navigation hook
import { useNavigation } from '#react-navigation/native';
And
export default function QrCodeScreen() {
const navigation = useNavigation();
...
Then
const handleBarCodeScanned = ({ type, data }) => {
navigation.navigate('nextScreen', { data1, data2 })
})}
I managed to solve the error by passing as the navigation parameter in the function declaration.
Before
export default function QrCodeScreen() {
}
After
export default function QrCodeScreen({navigation}) {
}
Change screen
navigation.navigate('SetupConnectionScreen');

Check if function is dispatching action with jest and enzyme

I have a Login Screen that has a method that when called dispatches an action. I want to test using jest and enzyme if when the button is pressed, this function is called and, therefore, the action is dispatched. I tried in many different ways, but I couldn't achieve this.
screens/login.js
import React, { Component } from 'react';
import {
KeyboardAvoidingView,
TextInput,
StyleSheet,
Text,
Button,
TouchableOpacity
} from 'react-native';
import { connect } from 'react-redux';
import { login } from 'actions/sessions.js';
export class LoginScreen extends Component {
constructor(props){
super(props);
this.state = {
email: '',
password: '',
error: ''
}
}
requestLogin = async () => {
if(this.checkFields()){
this.setState({error: ''});
this.props.loginRequest(this.state.email, this.state.password);
}
}
checkFields = () => {
let { email, password } = this.state;
const re = /^(([^<>()\[\]\\.,;:\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,}))$/;
if(!re.test(email)){
this.setState({error: 'E-mail must be valid'})
return false;
}else if(email == '' || password == ''){
this.setState({error: 'All fields are required!'});
return false
}else{
return true;
}
}
handleEmailText = (email) => {
this.setState({email})
}
handlePasswordText = (password) => {
this.setState({password})
}
render(){
return(
<KeyboardAvoidingView style={styles.container} enabled={true} behavior="padding">
<Text>{this.state.error || this.props.error}</Text>
<TextInput
onChangeText={(e) => this.handleEmailText(e)}
value={this.state.email}
keyboardType="email-address"
textContentType="emailAddress"
autoCapitalize="none"
placeholder="E-mail"
style={styles.input}
/>
<TextInput
onChangeText={(e) => this.handleEmailPassword(e)}
value={this.state.password}
placeholder="Password"
textContentType="password"
autoCapitalize="none"
secureTextEntry={true}
style={styles.input}
/>
<Button style={styles.button}
title="Sign In"
onPress={() => this.requestLogin()}/>
</KeyboardAvoidingView>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
input: {
width: "50%",
height: 40,
borderColor: 'gray',
borderWidth: 1,
paddingLeft: 20,
paddingRight: 20,
margin: 10
}
})
const mapDispatchToProps = dispatch => {
return {
loginRequest: (email, password) => dispatch(login(email, password))
}
}
const mapStateToProps = state => {
return {
error: state.sessions.error
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
tests/screens/login.test.js
import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import { LoginScreen } from 'screens/Login';
describe('LoginScreen', () => {
it('should dispatch login action when button clicked', async () => {
const mockStore = configureStore();
const initialState = {};
const store = mockStore(initialState);
const wrapper = shallow(<LoginScreen store={store}/>)
wrapper.setState({email: 'foo#bar.com', password: '1234'})
const component = await wrapper.dive();
component.find('button').simulate('click');
expect(store.getActions()).toMatchSnapshot();
});
})
When I have this approach, it says that simulate is meant to be run on 1 node. 0 found instead. I have no clue how to test it.
In react native you can use:
component.find('Button').props().onPress();
to simulate user interaction.
Also, you should use Button instead of button (your component name).

React Native - state is not saved in object

Im trying out React Native an now im fetching a weather forecast from openweather API. the data is getting fetched after the user type in the city an click the button.
The problem is that i am trying to save the response to the state objects property "forecast" but its not beeing saved.
What am i doing wrong?
import React, {Component} from 'react';
import {StyleSheet, Text ,TextInput, View, Button} from 'react-native';
export default class App extends Component {
constructor(props){
super(props);
this.state = {
text:"",
forecast:null,
hasData: false
}
}
userTextChange = (input) => {
this.setState({
text:input
})
}
getMovies = () => {
var url = 'https://api.openweathermap.org/data/2.5/weather?q='+this.state.text+'&units=metric&appid=7d6b48897fecf4839e128d90c0fa1288';
fetch(url)
.then((response) => response.json())
.then((response) => {
this.setState = ({
forecast:response,
hasData:true
})
console.log(response) <-- This is a json reponse with one object
})
.catch((error) => {
console.log("Error: ",error);
});
}
render() {
return (
<View style={styles.container} >
<TextInput
style={{width:'80%',borderRadius:8,marginTop:70,height:60,backgroundColor:'#f1f1f1',textAlign:'center',borderWidth:1,borderColor:'#ccc'}}
placeholder=""
onChangeText={this.userTextChange}
/>
<Button
title="Get forecats"
style={{backgroundColor:'#000',height:50,width:'50%',marginTop:30,marginBottom:30}}
onPress={()=>this.getMovies()}
/>
<View style={{width:'90%',height:'68%', backgroundColor:'rgba(0,0,0,0.5)',alignItems:'center',paddingTop:20}}>
<Text style={{color:'#000',fontSize:22}}>{this.state.forecast.name}</Text> <-- THIS IS NULL
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex:1,
alignItems:'center'
},
});
Herer is the JSON response frpm openweather API
The following line:
this.setState = ({
forecast:response,
hasData:true
})
should be:
this.setState({
forecast:response,
hasData:true
})
You should also consider initializing forecast in state to an empty object.