4

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="/" />
      )
    }
  />
);
Daniel P
  • 75
  • 1
  • 4

1 Answers1

14

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())

Joel Hager
  • 2,990
  • 3
  • 15
  • 44
  • 1
    Thankyou, that is very helpful :) – Daniel P Jun 23 '21 at 20:04
  • 1
    Let me know if you run into any issues! :) – Joel Hager Jun 24 '21 at 00:25
  • The login and logout methods work great. How would I use this with a private route? I think at the moment the code is rendering before checking authentication so the private route does not work. What is the best way to check this? – Daniel P Jun 24 '21 at 11:01
  • 1
    Depends on architecture. They have to be logged in, or also authorized? You would access the id on the user object with `currentUser.uid` – Joel Hager Jun 24 '21 at 17:20
  • 1
    You can also watch this popular video tutorial that walks through an implementation of Firebase authentication and useContext (the code in the video is nearly identical to that posted in this SO comment): https://www.youtube.com/watch?v=PKwu15ldZ7k&t=1428s – Green Sep 21 '22 at 17:03
  • So I’m using this, and NextJS, everytime I navigate to a new route, my Authcontext re renders and I can’t stop it from doing that. Also I was wondering what’s the point of using the auth context when I can just import the auth instance Via export const auth =getAuth(app) it – samplecode3300 Mar 30 '23 at 12:35
  • Can you show your code? – Joel Hager Mar 30 '23 at 18:14