DEV Community

Olorì Àṣàbí
Olorì Àṣàbí

Posted on

Creating an Authentication Form using React Hook Form, React and Firebase.

In this tutorial , we’ll be setting up our own Firebase project through the Firebase Console to setup user registration and authentication in our React application. Firebase simplifies the process of user authentication by providing all necessary functions and SDK to be integrated in your project and allowing you to focus on other parts of the development process.

Let’s get started by creating our application using Create React App and installing the Firebase, managing User State with React Context API and React Router dependencies by running the following commands but make sure you have the necessary requirements for authenticating with Firebase in React

  • Node.js installed
  • Code editor ( Visual Studio Code preferably )
  • Google account which will be to used in Firebase
  • Basic knowledge of React
npx create-react-app react-auth-form
cd react-auth-form
npm install firebase react-router-dom
Enter fullscreen mode Exit fullscreen mode

Create a Firebase account and add a "New Project".

Follow the provided prompts to complete the setup and you'll end up here:

Image description

To add Firebase to your app use the web option (</>).

To complete the Firebase setup we need to specify an authentication method. There are quite a number of options of methods available but for this tutorial we'll be using the email and password method. Navigate to "Authentication" -> "Sign-in Method" and change the "Email/Password" status setting to "Enabled".

Firebase config

Create a new file in the following location - src/firebase.js.

This file import's the Firebase SDK and contains the Firebase configuration settings and setting all inputs into your .env file with the standard variable names for secured purposes:

import { initializeApp } from "firebase/app"
import { getAuth } from "firebase/auth"
import "firebase/auth"
import { getFirestore } from '@firebase/firestore';

const firebaseConfig = {
    apiKey: process.env.REACT_APP_FIREBASE_KEY,
    authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
    projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
    storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID,
    appId: process.env.REACT_APP_FIREBASE_APP_ID,
    measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID
};

export const app = initializeApp(firebaseConfig)
export const auth = getAuth(app)
export const db = getFirestore(app);
Enter fullscreen mode Exit fullscreen mode

Copy these setting from "Project Settings" - "General" in the Firebase console.

Register users using React-hook-form

Create a new file in the following location - src/components/Register.js.

This component contains a sign up form so users can create accounts using react-hook-form:
To install React Hook Form, run the following command:

npm install react-hook-form

First, import the useForm Hook from the installedreact-hook-form package:

import { useForm } from "react-hook-form";

Then, inside your component, use the Hook as follows to validate errors, submit data and register:

const { handleSubmit, formState: { errors }, trigger, register, watch } = useForm();

To apply validations to a field in the form input, you can pass validation parameters to the register method. Validation parameters are similar to the existing HTML form validation standard.

These validation parameters include the following properties:

  • required indicates if the field is required or not. If this property is set to true, then the field cannot be empty

  • minlength and maxlength set the minimum and maximum length for a string input value

  • min and max set the minimum and maximum values for a numerical value

  • type indicates the type of the input field; it can be email, number, text, or any other standard HTML input types

  • pattern defines a pattern for the input value using a regular expression

Now lets collect data and register with firebase

import React from 'react'
import { createUserWithEmailAndPassword } from 'firebase/auth';
import { useForm } from 'react-hook-form';

const Register = ({ history }) => {
    const {
    handleSubmit,
    formState: { errors },
    trigger,
    register,
    watch
    } = useForm();

    async function onhandleSubmit(data) {
     //console.log(data)
        try {
        await createUserWithEmailAndPassword(
        auth, data.email, data.password, data.name)
        history.push("/");
        alert ("User Created Successfully")
        } catch (error) {
        console.log(error)
        alert ("User created failed")
        alert(error);
      }
    }

 return (
  <div>
    <Form onSubmit={handleSubmit(onhandleSubmit)}>
      <h5>Create an account</h5>
      <div>
        <div>
         <label>Your email address</label>
          <input
            id="email"
            name="email"
            type= 'email'
            required={true}
            {...register("email", {
            required: "Email is Required!!!" ,
            pattern: {
            value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
            message: "Invalid email address",
            }})}
            error={Boolean(errors.email)}
            onKeyUp={() => {trigger("email")}}
          ></input>
          {errors.email && (
          <small className="text-danger">{errors.email.message}</small>
          )}
        </div>
      <div>
        <label>Your password</label>
        <input
           name='password'
           id="password"
           type= 'password'
           autoComplete='off'
           className={`form-control ${errors.password && "invalid"}`}
           required={true}
           {...register("password", {
           required: "You must specify a password",
           pattern: {
           value: '^(?=.*?[A-Z])(?=(.*[a-z]){1,})(?=(.*[\d]){1,})(?=(.*[\W]){    1,})(?!.*\s).{8,}$',
           message: "Password should contain at least one number and one    special character"
           },
          minLength: {
          value: 8,
          message: "Password must be more than 8 characters"
          },
          maxLength: {
          value: 20,
          message: "Password must be less than 20 characters"
          },
          })}
          onKeyUp={() => {trigger("password")}}
          error={Boolean(errors.password)}
        ></input>
          {errors.password && (
          <small className="text-danger">{errors.password.message}</small>
          )}
      </div>
      <div>
        <label>Confirm your password</label>
        <input
           id="confirmPassword"
           name="confirmPassword"
           type='password'
           {...register( 'confirmPassword', {
           validate: value =>
           value === watch("password", "") || "The passwords do not match"
           })}
           autoComplete='off'
           onPaste={(e) =>{
           e.preventDefault();
           return false
           }}
           error={Boolean(errors.confirmPassword)}
           className={`form-control ${errors.confirmPassword && "invalid"}`}
           required={true}
           onKeyUp={() => {trigger("confirmPassowrd")}}
         />
           {errors.confirmPassword && (
           <small className="text-danger">{errors.confirmPassword.message}    </small>
           )}
      </div>
      <div>
        <label>Your full name</label>
        <input
           name='name'
           type="name"
           className={`form-control ${errors.name && "invalid"}`}
           required={true}
           defaultValue=""
          {...register("name", { required: "Fullname is Required!!!" })}
          onKeyUp={() => {trigger("name")}}/>
          {errors.name && (
          <small className="text-danger">Fullname is Required!!!</small>
         )}
        </div>
        <div>
          <button>Create an account</button>
        </div>
      </div>
    </Form>
  </div>
)}
export default withRouter(Register)
Enter fullscreen mode Exit fullscreen mode

Managing User State with React Context API

Context API is a way to share data with components at any level of the React component tree without having to pass it down as props. Since a user might be required by a different component in the tree, using the Context API is great for managing the user state.

Before we start using the Context API, there are a few things we need to set up:

  • Create a context object using the createContext() method
  • Pass the components we want to share the user state with as children of Context.Provider
  • Pass the value we want the children/consuming component to access as props to Context.Provider

Let’s get to it. In the src directory, create an AuthContext.js file and add the following lines of code to it:

import React, { useEffect, useState, useContext } from "react";
import { auth } from "./api";
import {
  createUserWithEmailAndPassword, 
  onAuthStateChanged,
  sendPasswordResetEmail,
  signInWithEmailAndPassword } from "firebase/auth";

export const AuthContext = React.createContext();

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

export const AuthProvider = ({ children }) => {
  const [currentUser, setCurrentUser] = useState(null);
  const [pending, setPending] = useState(true);

  function signup(email, password, name) {
   return createUserWithEmailAndPassword(auth, email, password, name)
  }

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

  function resetPassword(email) {
    return sendPasswordResetEmail(auth, email)
  }

  function updateEmail(email) {
   return currentUser.updateEmail(email)
  }

  function updatePassword(password) {
    return currentUser.updatePassword(password)
  }
  useEffect(() => {
   const unsubscribe = onAuthStateChanged(auth, user => {
    setCurrentUser(user)
    setPending(false)
   });
   return unsubscribe
  }, []);

  if(pending){
    return <>Loading...</>
  }

  const value = {
      currentUser,
      login,
      signup,
      logout,
      resetPassword,
      updateEmail,
      updatePassword
   }
    return (
      <AuthContext.Provider
      value={value}
      >
       {children}
      </AuthContext.Provider>
    );
};
Enter fullscreen mode Exit fullscreen mode

Create a new file in the following location — src/components/Login.js.

This component contains the log in form so users can sign into their account:

import React, { useContext } from "react";
import { Redirect } from "react-router-dom";
import { AuthContext } from "./Auth";
import firebaseConfig from "../firebase.js";
import { useForm, Controller } from 'react-hook-form';

const Login = () => {

  const {
   handleSubmit,
   control,
   formState: { errors },
  } = useForm();

  const submitHandler = async ({ email, password }) => {
    await try {
        firebaseConfig.auth().signInWithEmailAndPassword(email,   password);
      } catch (error) {
        alert(error);
      }
    };

    const { currentUser } = useContext(AuthContext);

    if (currentUser) {
      return <Redirect to="/dashboard" />;
    }
  return (
    <>
      <h1>Log In</h1>
      <form onSubmit={handleSubmit(submitHandler)}>
        <label for="email">Email</label>
        <input
         id="email"
         name="email"
         type= 'email'
         required={true}
         {...register("email", {
          required: "Email is Required!!!",
          pattern: {
          value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
           message: "Invalid email address",
           }})}
           error={Boolean(errors.email)}
           onKeyUp={() => {trigger("email")}}
          ></input>
        {errors.email && (
         <small className="text-danger">{errors.email.message}</small>
        )}
       </div>
       <div>
       <label>Your password</label>
       <input
         name='password'
         id="password"
         type= 'password'
         autoComplete='off'
         className={`form-control ${errors.password && "invalid"}`}
         required={true}
         {...register("password", {
         required: "You must specify a password", 
         pattern: {
         value: '^(?=.*?[A-Z])(?=(.*[a-z]){1,})(?=(.*[\d]){1,})
         (?=. (.*.      [\W]){    1,})(?!.*\s).{8,}$',
         message: "Password should contain at least one number
         and      one    special character"
          },
         minLength: {
         value: 8,
          message: "Password must be more than 8 characters"
           },
           maxLength: {
          value: 20,
          message: "Password must be less than 20 characters"
          },
          })}
          onKeyUp={() => {trigger("password")}}
          error={Boolean(errors.password)}
           ></input>
          {errors.password && (
          <small className="text-danger">  
          {errors.password.message}
          </small>
          )}
       </div>        
        <button type="submit">Submit</button>
      </form>
    </>
  );
};
export default Login;
Enter fullscreen mode Exit fullscreen mode

If the current user is already logged in they’ll get redirected to the home page. Otherwise we capture the form input on submit and send the details to the Firebase Authentication signInWithEmailAndPassword method.

Home page

Create a new file in the following location — src/components/Home.js.

This component contains content that can only be viewed by authenticated users:

import React from "react";
import { useAuth } from '../../services/auth';
import { useState } from 'react';
import { useHistory } from 'react-router-dom';

const Home = () => {
  const [error, setError] = useState("")
  const { currentUser, logout } = useAuth()
  const history = useHistory()

  async function handleLogout() {
    setError("")
    try {
      await logout()
      history.push("/login")
   } catch {
      setError("Failed to log out")
          }
   }

return (
    <div>
      <h1>Welcome</h1>
      <p>This is the Home Page, if you can see this you're logged in.
       </p>
      <button onClick={handleLogout}>Sign out</button>
    </div>
  );
};
export default Home;
Enter fullscreen mode Exit fullscreen mode

If this page is accessed by a non-authenticated user the browser will re-direct to the login page. If the user is authenticated we display the private content. We’ve also included a button so users can sign out of their account.

Pulling it all together in App.js

Modify the App.js file to include the following:

import './App.css';
import {
BrowserRouter as Router,
Route,
} from "react-router-dom";
import Register from "./pages/Register/Register";
import Login from "./pages/Login/Login";
import { AuthProvider } from "./services/auth";
import PrivateRoute from "./PrivateRoute";
import Profile from "./pages/Profile/Profile";
import Home from "./pages/Home/Home";

function App() {

return (
  <AuthProvider>
    <Router>
      <div>
        <PrivateRoute exact path="/" component={Home} />
        <Route  path="/login" component={Login} />
        <Route path="/register" component={Register} />
      </div>
    </Router>
  </AuthProvider>
)}
export default App;
Enter fullscreen mode Exit fullscreen mode

You can now run npm start to start the application and test out the registration and authentication process. Once the sign up form has been submitted you can browse to “Authentication” - “Users” in the Firebase console to confirm registration was successful and manage user accounts.

Image description

You now know how to authenticate users in your React applications using Firebase. If you would like to learn more about Firebase Authentication, I’d suggest checking out the official guide.

Conclusion

In this tutorial, we have learned how to use of the Firebase Authentication to build user registration and authentication service in React using React Hook Form for form validation.

References

Top comments (5)

Collapse
 
servatj profile image
Josep

Can you share the github repo ? There are a lot of thing that can be hard to follow, also as a bit of feedback highlight the examples properly can help con readability.

Nice post, cheers :)

Collapse
 
oakreed profile image
OakReed

How do you make authentication in React?
Vashikaran Specialist In Faridabad

Collapse
 
oloriasabi profile image
Olorì Àṣàbí

Can you please be more specific on your question ?

Collapse
 
bluebill1049 profile image
Bill

Thank you for writing this blog post. ❤️

Collapse
 
catherinegs profile image
catherinegs

Thanks for your post