0
votes

I know this question has been asked in other contexts. I've read those answers. I've tried the answers. I've read through the firebase docs. So, far it's not helping. I'm new to firebase. Learning how to use the product by going through a tutorial on using React and Firebase together. Building an authentication/authorization module for users. Can successfully register/SignUp a user and see the results in both the authorization module and the realtime database in the firebase project overview. When the user Signs Out, a check of the RDb confirms that all the data properties persist. When the user Signs In (email/password) again, a check of the RDb shows that only the uid and the email address persist in the RDb. The non-auth properties of the user like username and role become empty strings or objects, present but not populated. I have done considerable reading of the firebase docs and debugging of the code, but I cannot determine why this happens and therefore cannot determine how to fix it.

Here are some code segments:

First the firebase module:

import app from 'firebase/app'
import 'firebase/auth'
import 'firebase/database'

... set up the config ...

// set the config constant to the appropriate value
const config = process.env.NODE_ENV === 'production' ? prodConfig : devConfig

class Firebase {
  constructor() {
    // initialize the app using the Firebase config to instatiate all of the firebase services
    app.initializeApp(config)

    // instantiate the initial auth state within the app
    this.auth = app.auth()  // Gets the Auth service for the current app

    // instantiate the database within the app
    this.db = app.database()  // Gets the Database service for the current app
    console.log('this.db ', this.db)

    // Social media login providers (authorization methods within Firebase)
    this.googleProvider = new app.auth.GoogleAuthProvider()
    this.facebookProvider = new app.auth.FacebookAuthProvider()
    this.twitterProvider = new app.auth.TwitterAuthProvider()
  }

  ... the comments are mostly for my benefit ...

  // ***** Firebase Auth API *****
  // create user with email and password (equate doCreate... with Firebase createUser...)
  doCreateUserWithEmailAndPassword = ( email, password ) => this.auth.createUserWithEmailAndPassword( email, password )
  // ***** SignIn with email ***** (equate doSignIn... with Firebase signIn...)
  doSignInWithEmailAndPassword = ( email, password ) => this.auth.signInWithEmailAndPassword( email, password )
  // ***** SignIn with facebook ***** (equate as above)
  doSignInWithFacebook = () => this.auth.signInWithPopup( this.facebookProvider )
  // ***** SignIn with google ***** (equate as above)
  doSignInWithGoogle = () => this.auth.signInWithPopup( this.googleProvider )
  // ***** SignIn with twitter ***** (equate as above)
  doSignInWithTwitter = () => this.auth.signInWithPopup( this.twitterProvider )
  // ***** SignOut ***** (equate as above)
  doSignOut = () => this.auth.signOut()
  // ***** Password Reset ***** (equate as above)
  doPasswordReset = email => this.auth.sendPasswordResetEmail( email )
  // ***** Password Update ***** (equate as above)
  doPasswordUpdate = password => this.auth.currentUser.updatePassword( password )

  // ***** User API ***** 
  // set the current user id
  user = uid => this.db.ref(`users/${uid}`)
  // set the reference to the users collection in the firebase database
  users = () =>this.db.ref('users')

  // ***** Merge Auth and DB User API *****

  ... big block of comments to me ...

  onAuthUserListener = (next, fallback) =>
    this.auth.onAuthStateChanged(authUser => {  
      if ( authUser ) {                         
        this.user(authUser.uid)                 
          .once('value')                        
          .then(snapshot => {                          
            const dbUser = snapshot.val()
            console.log('this.user ',this.user)    
            console.log('dbUser ',dbUser)
            // default empty roles
            if ( !dbUser.roles ) {
              dbUser.roles = {}
            }
            // merge auth and db user
            this.db.ref(`users/${this.user}`).set({
              uid: authUser.uid,
              email: authUser.email,
              username: authUser.username,
              roles: authUser.roles,
              ...dbUser,
            })
            console.log('firebase.js authUser ', authUser)
            next(authUser)
          })
      } else {                                  //  there is no authUser
        fallback()
      }
    })
}

export default Firebase

Here is the Sign UP component:

// index.js - SignUp
//  the entry point for the SignUp component
import React, { Component } from 'react'
import { Link, withRouter } from 'react-router-dom'
import { compose } from 'recompose'

import { Typography, Input, Checkbox, FormLabel, Button } from '@material-ui/core'

import { withFirebase } from '../Firebase'
import * as ROUTES from '../../constants/routes'
import * as ROLES from '../../constants/roles'

import '../../styles/auth.css'

const SignUpPage = () => (
  <div id='wrapper' className='signup-page'>
    <SignUpForm />
  </div>
)

// initialize the state of the component using destructuring
// allows INITIAL_STATE to be reset after successful SignUp
const INITIAL_STATE = {
  username: '',
  email: '',
  passwordOne: '',
  passwordTwo: '',
  isAdmin: false,
  error: null,
}

class SignUpFormBase extends Component {
  constructor(props) {
    super(props)

    // spread operator (...) spreads out to reach all properties individually
    this.state = { ...INITIAL_STATE }
  }

  onSubmit = event => {
    // get necessary info from this.state to pass to the Firebase authentication API
    const { username, email, passwordOne, isAdmin } = this.state
    const roles = {}
    if ( isAdmin ) { roles[ROLES.ADMIN] = ROLES.ADMIN } //  set roles if the admin checkbox is checked

    this.props.firebase
      // create a user (limited access) in the authentication database
      .doCreateUserWithEmailAndPassword( email, passwordOne )
      // successful 
      .then( authUser => {
        // create a user in Firebase realtime database -- this is where you manage user properties
        // because in the firebase auth module, users cannot be manipulated.
        console.log('signup authUser ',authUser)
        return this.props.firebase
          .user(authUser.user.uid)          //  use the authUser.uid to:
          .set({ username, email, roles })  //  write username, email & roles to the rdb
      })
      .then(() => {
        // update state and redirect to Home page
        this.setState({ ...INITIAL_STATE })
        this.props.history.push(ROUTES.HOME)
      })
      // error - setState, error (if something is wrong)
      .catch(error => {
        this.setState({ error })
      })
    // prevent default behavior (a reload of the browser)
    event.preventDefault()
  }

  onChange = event => {
    // dynamically set state properties when they change, based on which input call is executed
    // each <input> element (in the return) operates on a different property of state (according to value)
    this.setState({ [event.target.name]: event.target.value })
  }

  onChangeCheckbox = event => {
    this.setState({ [event.target.name]: event.target.checked })
  }

  render() {
    // parse each of the values from current state
    const {
      username,
      email,
      passwordOne,
      passwordTwo,
      isAdmin,
      error
    } = this.state
    // list of invalid conditions for which to check (validation of form elements)
    const isInvalid =
      passwordOne !== passwordTwo ||
      passwordOne === '' ||
      email === '' ||
      username === ''
    return (
      // the input form -- with fields (username, email, passwordOne, passwordTwo)
      <div className='container'>
        <Typography 
          variant='h6' 
          align = 'center' 
          className='item'
        >
          Sign Up Page
        </Typography>
        <br />
        <form className='signup-form item' onSubmit={ this.onSubmit }>
          <Input
            className='item'
            name='username'
            value={username}
            onChange={this.onChange}
            type='text'
            placeholder='Full Name'
          />

... input email, password, confirm password, checkbox to designate Admin ...

          <Button
            variant='contained'
            className='item btn btn-secondary' 
            disabled={ isInvalid } 
            type='submit'
          >
            Sign Up
          </Button>

          {/* if there is an error (a default Firebase property), render the error message */}
          {error && <p>{ error.message }</p>}
        </form>
      </div>
    )
  }
}

const SignUpLink = () => (
  <Typography 
    variant = 'body1' 
    align = 'center' 
    className = 'item' 
  >
    Don't have an account? <Link to={ ROUTES.SIGN_UP }>Sign Up</Link>
  </Typography>
)

const SignUpForm = compose(withRouter, withFirebase)(SignUpFormBase)

export default SignUpPage

export { SignUpForm, SignUpLink }

Here is the Sign IN (email/password) component:

// SignInEmail.js - SignIn
//  the email/password SignIn component
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'

import { Typography, Input } from '@material-ui/core'
import Button from 'react-bootstrap/Button'

import { withFirebase } from '../Firebase'
import * as ROUTES from '../../constants/routes'

// initialize the state of the component using destructuring
// allows INITIAL_STATE to be reset after successful SignUp
const INITIAL_STATE = {
  username: '',
  email: '',
  password: '',
  roles: {},
  error: null,
}

// ======================================
// ***** signin with email/password *****
// ======================================
class SignInEmailBase extends Component {
  constructor(props) {
    super(props)

    // spread operator (...) spreads out to reach all properties individually
    this.state = {
      ...INITIAL_STATE
    }
  }

  onSubmit = event => {
    // get necessary info from this.state to pass to the Firebase authentication API
    // const { username, email, password, roles } = this.state
    const { username, email, password, roles } = this.state

    this.props.firebase
      // execute SignIn function (create a user)
      .doSignInWithEmailAndPassword( email, password )
      // successful 
      .then(authUser => {
        // create a user in Firebase Realtime database
        console.log('signin authUser ',authUser)
        return this.props.firebase
          .user(authUser.user.uid)
          .set({ username, email, roles })
      })
      .then(() => {
        // update state and redirect to Home page
        this.setState({ ...INITIAL_STATE })
        this.props.history.push(ROUTES.HOME)
      })
      // error - setState, error (if something is wrong)
      .catch(error => {
        this.setState({
          error
        })
      })
    // prevent default behavior (a reload of the browser)
    event.preventDefault()
  }

  onChange = event => {
    // dynamically set state properties when they change, based on which input call is executed
    // each <input> element (in the return) operates on a different property of state (according to value)
    this.setState({ [event.target.name]: event.target.value })
  }

  render() {
    // parse each of the values from current state
    const { email, password, error } = this.state

    // list of invalid conditions for which to check (validation of form elements)
    const isInvalid = password === '' || email === ''

    return (
      // the input form -- with fields (username, email, passwordOne, passwordTwo)
      <div className = 'container signin-page'>
        <Typography 
          variant = 'h6' 
          align = 'center' 
          className = 'item' 
        >
          Sign In Page
        </Typography> 
        <br />
        <form className = 'item email-form' onSubmit = { this.onSubmit} >

          ... input email, password...

          { /* disable the button if the form is invalid -- see isInvalid above */ } 
          <Button 
            className = 'item btn btn-secondary' 
            type = 'submit' 
            disabled = { isInvalid } 
          >
            SIGN IN WITH EMAIL
          </Button>

          { /* if there is an error (a default Firebase property), render the error message */ }
          { error && <p> { error.message } </p> } 
        </form>
      </div>
    )
  }
}

const SignInEmail = compose(withRouter, withFirebase, )(SignInEmailBase)

export default SignInEmail

Here is the screenshot of the browser inspect and the screenshot of the Realtime Database entry after Sign UP inspect after sign up

Rdb after sign up

Here is the same user after sign out and sign in -- no other data manipulation

inspect same user after sign out and sign in

Rdb same user after sign out sign in

I sure could use some help. Thanks.

1

1 Answers

0
votes

I sort of stumbled on to the answer to this myself. As I was reviewing my code and comparing the SignUp component with the SignIn component I realized that looked out of place within the SignIn component. It was this block within the onSubmit block:

this.props.firebase
      // execute SignIn function (create a user)
      .doSignInWithEmailAndPassword( email, password )
      // successful 
      .then(authUser => {
        // create a user in Firebase Realtime database
        return this.props.firebase
          .user(authUser.user.uid)
          .set({ username, email, roles })
      })
      .then(() => {
        // update state and redirect to Home page
        this.setState({ ...INITIAL_STATE })
        this.props.history.push(ROUTES.HOME)
      })
      // error - setState, error (if something is wrong)
      .catch(error => {
        this.setState({
          error
        })
      })

It is identical to a similar block in the signUp component. It is necessary with the sign Up because I am creating a user with a signUp and the information that was disappearing in the signIn component is being entered in the signUp component so it is temporarily available in the authUser for transfer to the RdB. That information is not being entered in the signIn component so it is not available to transfer to the Realtime Database and hence (because of the way firebase works in syncing users between the auth module and the RdB) those properties were being over written with empty strings or empty objects. Further because the function is a signIn function there is no need at signIn time to update the RdB so the solution is to eliminate this block of code in the in the signIn component.

      .then(authUser => {
        // create a user in Firebase Realtime database
        return this.props.firebase
          .user(authUser.user.uid)
          .set({ username, email, roles })
      })

Sorry if I was a bother. But I did want to post that I figured it out.