HuLu ViCa
HuLu ViCa

Reputation: 5456

Can't understand how to use React Native useEffect

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.

Upvotes: 2

Views: 1253

Answers (4)

thamaphyr
thamaphyr

Reputation: 51

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

Upvotes: 1

Edison Sanchez
Edison Sanchez

Reputation: 104

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.

Upvotes: 0

Abraham
Abraham

Reputation: 9885

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...

Upvotes: 1

strdr4605
strdr4605

Reputation: 4362

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

Upvotes: 0

Related Questions