Daniel P
Daniel P

Reputation: 75

React - Firebase authentication and useContext

I'm trying to figure out the best way to create a global state variable that will hold a firebase authentication user id. For example the below code would check if a user is logged in and then send them to welcome page if successful.

But I also need to setup up private routes on a different file, I want to be able to share the getId state. I read that useContext can do this but unsure how to implement it. Please advise, thanks

const [getId, setId] = useState("");

const login = async ( id ) => {
    return setId(id);
  };

firebase.auth().onAuthStateChanged((user) => {
    if (user) {
      login(user.uid).then(() => {
       
    history.push("/welcome");
        
      });
    } else {
     
      history.push("/");
     
    }
  });
const PrivateRoute = ({ getId, component: Component, ...rest }) => (
  <Route
    {...rest}
    component={(props) =>
      getId ? (
        <div>
         
          <Component {...props} />
        </div>
      ) : (
        <Redirect to="/" />
      )
    }
  />
);

Upvotes: 4

Views: 8282

Answers (1)

Joel Hager
Joel Hager

Reputation: 3442

I'll give you my example to have an Auth Context. Here are the parts:

The _app.js file:

import '../styles/globals.scss'
import { motion, AnimatePresence } from 'framer-motion'
import { useRouter } from 'next/router'
import Header from '../components/Header'
import Footer from '../components/Footer'
import { AuthProvider } from '../contexts/AuthContext'
import { CartProvider } from '../contexts/CartContext'
import { ThemeProvider } from '@material-ui/core'
import theme from '../styles/theme'


export default function App({ Component, pageProps }) {
  const router = useRouter()

  return(
    <AnimatePresence exitBeforeEnter>
      <CartProvider>
        <AuthProvider>
          <ThemeProvider theme={theme}>
            <Header />
            <motion.div key={router.pathname} className="main">
              <Component { ...pageProps } />
              <Footer />
            </motion.div>
          </ThemeProvider>
        </AuthProvider>
      </CartProvider>
    </AnimatePresence>
  )
}

The item of significance is the <AuthProvider> component. That's where the context is wrapped.

The AuthContent.js file:

import { createContext, useContext, useEffect, useState } from 'react'
import { auth } from '../firebase'

const AuthContext = createContext()

export function useAuth() {
  return useContext(AuthContext)
}

export function AuthProvider({ children }) {
  const [currentUser, setCurrentUser] = useState()
  const [loading, setLoading] = useState(true)

  function login(email, password) {
    return auth.signInWithEmailAndPassword(email, password)
  }

  function signOut() {
    return auth.signOut();
  }

  function signUp(email, password) {
    return auth.createUserWithEmailAndPassword(email, password)
  }

  function getUser() {
    return auth.currentUser
  }

  function isAdmin() {
    return auth.currentUser.getIdTokenResult()
    .then((idTokenResult) => {
      if (!!idTokenResult.claims.admin) {
        return true
      } else {
        return false
      }
    })
  }

  function isEditor() {

  }

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged(user => {
      setCurrentUser(user)
      setLoading(false)
    })

    return unsubscribe
  }, [])

  const value = {
    currentUser,
    getUser,
    login,
    signOut,
    signUp
  }

  return (
    <AuthContext.Provider value={value}>
      { !loading && children }
    </AuthContext.Provider>
  )

}

This is where the state is stored and accessed, including all of the helpers (signup, signout, login, etc).

How to use it:

import { Button, Card, CardHeader, CardContent, Link, TextField, Typography } from '@material-ui/core'
import { motion } from 'framer-motion'
import { useRef, useState } from 'react'
import { useAuth } from '../contexts/AuthContext'
import { useRouter } from 'next/router'

export default function SignupForm() {

  const router = useRouter()
  const { signUp } = useAuth()
  const [state, setState] = useState({
    email: "",
    password: "",
    passwordConfirm: ""
  })
  const [error, setError] = useState("")

  function handleForm(e) {
    setState({
      ...state,
      [e.target.name]: e.target.value
    })
  }

  async function handleSubmit(e) {
    if (state.password !== state.passwordConfim) {
      setError("Passwords do not match")
    }
    await signUp(state.email, state.password)
    .catch(err => console.log(JSON.stringify(err)) )
    router.push("/account")
  }

  return(
    <motion.div>
      <Card >
        <CardHeader title="Header" />
        <CardContent>
          <TextField label="email" name="email" variant="outlined" onChange={ handleForm } />
          <TextField label="password" name="password" type="password" variant="outlined" onChange={ handleForm } />
          <TextField label="Password Confirmation" name="passwordConfirm" type="password" variant="outlined" onChange={ handleForm } />
          {error && <Alert severity="error" variant="filled" >{error}</Alert>}
          <Button onClick={ handleSubmit }>
            <Typography variant="button">Sign Up</Typography>
          </Button>
        </CardContent>
      </Card>
    </motion.div>
  )
}

You import { useAuth } from your context (I usually put mine in a context folder) and then you can invoke instances of the variables inside the component by destructuring (e.g. const { currentUser, login } = useAuth())

Upvotes: 17

Related Questions