4

I am trying to create a login and signup process for a react-native mobile app with firebase authentication/database.

I can successfully login with pre existing email/passwords in Login.js.

My problem arises when I attempt to create a new account in SignUp.js

The user is added to firebase authentication correctly but I want my user profiles to have more information. This is why I am writing to the database with their name, email, and other information I have removed from the code on line 33 in Signup.js with the helper function at line 52.

My error is undefined is not an object(evaluating this.state.email), coming from line 33 in SignUp.js

This does not make sense to me as I can create the user successfully on line 27 of SignUp.js using this.state.email

Is this.state.email going out of scope? Any help is greatly appreciated.

App.js

import React from 'react';
import { StackNavigator } from 'react-navigation';
import * as firebase from 'firebase'; 
import Dashboard from './components/Dashboard';
import Login from './components/Login';
import SignUp from './components/SignUp';

const Application = StackNavigator({
    Login: { screen: Login},
    SignUp: { screen: SignUp},
    Dashboard: { screen: Dashboard },
});

export default class App extends React.Component {
    componentWillMount(){
        const firebaseConfig = {
            apiKey: 'MY KEY',
            authDomain: 'MY DOMAIN',
            databaseURL: 'MY DATABASE URL',
        }
        if (!firebase.apps.length) {
            firebase.initializeApp(firebaseConfig);
        }
    }
    render() {
        return (
            <Application/>
        )}}

Login.js

import React from 'react';
import { StyleSheet, Text, View, KeyboardAvoidingView, ImageBackground } from 'react-native';
import { StackNavigator } from 'react-navigation';
import { Button } from 'react-native-elements';
import { Input } from './Input';
import Dashboard from './Dashboard';
import * as firebase from 'firebase';

export default class Login extends React.Component {
    constructor(props){
        super(props)
        this.state = {
            email: '',
            password: '',
        }
    }

    loginUser = (email, password, navigate) => {
        try{
            firebase.auth().signInWithEmailAndPassword(email, password)
            .then(function(user){
                console.log(user);
                navigate('Dashboard', {email, password});
            })
        }
        catch (error){
            alert('No known user for that email and password combination')
            console.log(error.toString());
        }
    }

    static navigationOptions = { header: null }

    render() {
        const{ navigate } = this.props.navigation;
        return (
            <KeyboardAvoidingView
                behavior='padding'>
                <Input
                    placeholder = 'Email'
                    onChangeText = {email => this.setState({email})}
                    value = {this.state.email}/>
                <Input
                    placeholder = 'Password'
                    secureTextEntry
                    onChangeText = {password => this.setState({password})}
                    value = {this.state.password}/>
                <Button
                    title = 'Log in'
                    onPress = {() => this.loginUser(this.state.email, this.state.password, navigate)}/>
                <Button
                    title = 'Sign up'
                    onPress = {() => navigate('SignUp')}/>
            </KeyboardAvoidingView>
        );
    }
}

SignUp.js

import React from 'react';
import { StyleSheet, Text, View, KeyboardAvoidingView, ImageBackground } from 'react-native';
import { StackNavigator } from 'react-navigation';
import { Button } from 'react-native-elements';
import { Input } from './Input';
import Dashboard from './Dashboard';

import * as firebase from 'firebase';

export default class SignUp extends React.Component {

    constructor(props){
        super(props)
        this.state = {
            firstName: '',
            lastName: '',
            email: '',
            password: '',
            confirmPassword: '',
        }
    }

    signUpUser = () => {
        try{
            if(this.state.password === this.state.confirmPassword){
                console.log('CREATING USER...');
                firebase.auth().createUserWithEmailAndPassword(this.state.email, this.state.password).then( response => {
                    console.log('SIGNING IN...');
                    firebase.auth().signInWithEmailAndPassword(this.state.email, this.state.password)
                    firebase.auth().onAuthStateChanged(function(user) {
                        if (user) {
                            console.log('WRITING TO DATABASE...');
                            this.writeUserData(user, this.state.email, this.state.firstName, this.state.lastName);
                        }
                        else {
                            alert('Something went wrong. Please try again.');
                            return;
                        }
                    });
                })
            }
            else{
                alert('Passwords do not match');
                return;
            }
        }
        catch(error){
            console.log(error.toString());
        }
    }

    writeUserData = (user, email, first, last) => {
        console.log('ADDING USER ' + user.uid)
        try{
            firebase.database().ref('users/' + user.uid).set({
                email: email,
                first: first,
                last: last,
            });
        }
        catch(error){
            console.log(error.toString());
        }
        console.log('WRITE COMPLETE')
        //navigate to dashboard
    }

    static navigationOptions = { header: null }

    render() {
        const{ navigate } = this.props.navigation;
        return (
            <KeyboardAvoidingView
                behavior='padding'>
                <Input
                   placeholder = 'First Name'
                   onChangeText = {firstName => this.setState({firstName})}
                   value = {this.state.firstName}/>
                <Input
                    placeholder = 'Last Name'
                    onChangeText = {lastName => this.setState({lastName})}
                    value = {this.state.lastName}/>
                <Input
                    placeholder = 'Email'
                    onChangeText = {email => this.setState({email})}
                    value = {this.state.email}/>
                <Input
                    placeholder = 'Password'
                    secureTextEntry
                    onChangeText = {password => this.setState({password})}
                    value = {this.state.password}/>
                <Input
                    placeholder = 'Confirm password'
                    secureTextEntry
                    onChangeText = {confirmPassword => this.setState({confirmPassword})}
                    value = {this.state.confirmPassword}/>

                <Button
                    title = 'Create Account'
                    onPress = {() => this.signUpUser()}/>
                <Text
                    activeOpacity={0.75}
                    onPress = {() => this.props.navigation.goBack()}>
                    Go back
                </Text>
            </KeyboardAvoidingView>
        );
    }
}
David Owens
  • 645
  • 6
  • 25
  • 1
    Does it work with an arrow function? (i.e. change `function(user)` to `user =>`) Anonymous functions do weird things with scope sometimes. – John Montgomery Feb 06 '18 at 23:37

2 Answers2

12

this changes meaning when it is in a different function. This is basically why object methods work in Javascript, because this always refers to the class containing it. When you use this in an anonymous function, like you do in your SignUp.js file, you are not using the same this as before.

A simple workaround is to add a line like:

let self = this;

Or, if you only need the state:

let state = this.state;

Before the firebase.auth().onAuthStateChanged(function(user) { bit. Then inside, you use the variable you created instead of this.


Another thing to try is to change your anonymous function into an arrow function. Arrow functions do not modify the this variable. So, something like:

firebase.auth().onAuthStateChanged((user) => {
Aurel Bílý
  • 7,068
  • 1
  • 21
  • 34
  • Thank you for your response. If i were to use `let state = this.state` how would I call `writeUserData()`, that function is giving an undefined error. – David Owens Feb 06 '18 at 23:44
  • 1
    Disregard previous comment, your "another thing" worked perfectly. thanks again. – David Owens Feb 06 '18 at 23:46
  • 1
    @DavidOwens Ah, sorry. If you in fact need more variables from the original `this` object, it is better to use the first variant. Then `this.state` would become `self.state` and `this.writeUserData` would become `self.writeUserData`. Or change the anonymous function to an arrow function, which is the simpler solution. – Aurel Bílý Feb 06 '18 at 23:47
  • 1
    This is a very common issue in React and React Native. You will become a great sleuth soon enough. The trick is to start thinking immediately about what `this` might be referring to when you see errors about `cannot ___ of undefined` when you expect it to be defined. – agm1984 Feb 06 '18 at 23:51
  • So, if I'm following this correctly, the reason it was `undefined` was because the function itself had no `state` variables? When I tried to access `this.state.email` there was no `email` in the anonymous functions `state`? Correct? – David Owens Feb 06 '18 at 23:55
  • 1
    @DavidOwens Yes! When you use an anonymous `function`, inside it `this` actually refers to the function itself. A default Javascript function has no `state` variable. – Aurel Bílý Feb 06 '18 at 23:57
  • 1
    Learn something new everyday! Thanks @AurelBílý – David Owens Feb 06 '18 at 23:57
0

Try using a normal javascript function for the call back rather than the ES6 fat arrow. so instead of response => {...} use function(response){...}

this article may be able to help you understand the scope of the fat arrow function more.

gastonche
  • 463
  • 5
  • 19
  • 2
    Arrow functions *do not* change `this`, unlike regular functions. OP says the error shows on line 33 of `SignUp.js`, meaning line 27, inside the arrow function, works fine. You are not solving the problem, but introducing another scope. – Aurel Bílý Feb 06 '18 at 23:45