DEV Community

Cover image for React Firebase Auth with Redux deployed on Vercel
Momin Mahmud
Momin Mahmud

Posted on

React Firebase Auth with Redux deployed on Vercel

Motivation

Temporarily, I'm a full-time front-end developer for a project. It utilizes Redux, but when I started, I lacked both React and Redux experience. While I've been on this project for three months now, I've primarily relied on existing code to implement Redux functionalities without grasping the core concepts. Understanding the intricacies of the Redux store and its inner workings remained elusive. Thankfully, with guidance from senior colleagues and Stack Overflow, I've managed to navigate runtime issues.

However, I'm determined to delve deeper and solidify my understanding of Redux from the ground up. This way, I can troubleshoot problems with confidence and rely less on external resources for minor challenges.

To efficiently learn two technologies simultaneously, I decided to implement Firebase Authentication while storing user metadata and auth tokens using Redux.


Setup (Firebase)

Create a Firebase project:
Image description

Go to the Firebase console (https://console.firebase.google.com/) and create a new project.
Give your project a name and enable billing if necessary.

Register your app:

Image description

Select the platform you're developing for (web, Android, iOS, etc.) and follow the specific instructions for registering your app.
This will give you a configuration file or snippet that you need to add to your project.
** Install the Firebase SDK:**

Image description
Use the package manager for your platform (npm for JavaScript, CocoaPods for iOS, etc.) to install the required Firebase SDK libraries.
Initialize Firebase in your app:

Use the configuration file or snippet you obtained earlier to initialize Firebase in your app.
This will enable you to use Firebase services in your code.

Create Register Component

Notice, I the code I also have sent another call to sendEmailVerification so user cannot register with a bogus email


const Register = () => {
  const navigate = useNavigate();
  const [loading, setLoading] = useState(false);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const register = async () => {
    setLoading(true);
      await createUserWithEmailAndPassword(auth, email, password)
        .then(async (userCredential: any) => {
          await sendEmailVerification(userCredential.user);
          navigate(RouteEnums.VERIFICATION_NOTICE, { state : userCredential.user.email });
        })
        .catch((error: any) => {
          toast.error(error.message);
        }).finally(()=> {
          setLoading(false)
        });
  };

  return (
    <>
      {loading ? <Spinner /> : (
           <div className="flex min-h-full flex-1 flex-col justify-center w-[400px] px-6 py-12 lg:px-8">
           <div className="sm:mx-auto sm:w-full sm:max-w-sm">
             <img
               className="mx-auto h-10 w-auto"
               src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600"
               alt="Your Company"
             />
             <h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
               Register a new account
             </h2>
           </div>

           <div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
             <div>
               <label
                 htmlFor="email"
                 className="block text-sm font-medium leading-6 text-gray-900"
               >
                 Email address
               </label>
               <div className="mt-2">
                 <input
                   onChange={(e) => setEmail(e.target.value)}
                   id="email"
                   name="email"
                   type="email"
                   autoComplete="email"
                   required
                   className="block w-full rounded-md border-0 p-1.5 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                 />
               </div>
             </div>

             <div className="my-5">
               <div className="flex items-center justify-between">
                 <label
                   htmlFor="password"
                   className="block text-sm font-medium leading-6 text-gray-900"
                 >
                   Password
                 </label>
                 <div className="text-sm">
                   <a className="font-semibold text-indigo-600 hover:text-indigo-500">
                     Forgot password?
                   </a>
                 </div>
               </div>
               <div className="mt-2">
                 <input
                   onChange={(e) => setPassword(e.target.value)}
                   id="password"
                   name="password"
                   type="password"
                   autoComplete="current-password"
                   required
                   className="block w-full rounded-md border-0 p-1.5  shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                 />
               </div>
             </div>

             <div>
               <button
                 onClick={register}
                 type="submit"
                 className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
               >
                 Register
               </button>
             </div>
           </div>
         </div>
      )}

    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

Verify the Register Function

If your configuration was successful your user would appear in the Authentication > Userssection on the firebase console

Image description

Create Login component

Notice I have only let user into my application if the user's email is verified

const Login = () => {
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const [loading, setLoading] = useState(false);

useEffect(() => {
  console.log(loading)
},[loading])

  const checkEmailVerified = (userCredential: any) => {
    if (!userCredential.user.emailVerified) {
      console.error("User email is not verified.");
      toast.error("Please verify your email address to continue.");
    } else {
      dispatch(saveUser(userCredential.user));
      dispatch(saveToken(userCredential.user.accessToken));
      navigate(RouteEnums.HOME);
    }
  };
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const login = () => {
    setLoading(true);
      signInWithEmailAndPassword(auth, email, password)
        .then((userCredential: any) => {
          checkEmailVerified(userCredential);
        })
        .catch((error: any) => {
          toast.error(error.message);
        }).finally(()=> {
          setLoading(false)
        });
  };

  return (
    <>
      {loading ? <Spinner /> : (
              <div className="flex min-h-full flex-1 flex-col justify-center w-[400px] py-12 lg:px-8">
              <div className="sm:mx-auto sm:w-full sm:max-w-sm">
                <img
                  className="mx-auto h-10 w-auto"
                  src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600"
                  alt="Your Company"
                />
                <h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
                  Login
                </h2>
              </div>

              <div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
                <div>
                  <label
                    htmlFor="email"
                    className="block text-sm font-medium leading-6 text-gray-900"
                  >
                    Email address
                  </label>
                  <div className="mt-2">
                    <input
                      onChange={(e) => setEmail(e.target.value)}
                      id="email"
                      name="email"
                      type="email"
                      autoComplete="email"
                      required
                      className="block w-full rounded-md border-0 p-1.5 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                    />
                  </div>
                </div>

                <div className="my-5">
                  <div className="flex items-center justify-between">
                    <label
                      htmlFor="password"
                      className="block text-sm font-medium leading-6 text-gray-900"
                    >
                      Password
                    </label>
                    <div className="text-sm">
                      <a className="font-semibold text-indigo-600 hover:text-indigo-500">
                        Forgot password?
                      </a>
                    </div>
                  </div>
                  <div className="mt-2">
                    <input
                      onChange={(e) => setPassword(e.target.value)}
                      id="password"
                      name="password"
                      type="password"
                      autoComplete="current-password"
                      required
                      className="block w-full rounded-md border-0 p-1.5  shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                    />
                  </div>
                </div>

                <div>
                  <button
                    onClick={() => login()}
                    type="submit"
                    className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
                  >
                    Login
                  </button>
                </div>
              </div>
            </div>
      )}

    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

πŸŽ‰ Congratulations πŸŽ‰ you have implemented Auth using Firebase in your React app

Now Lets save the response from firebase in the Redux Store

Install the required packages:

Bash

npm install @reduxjs/toolkit react-redux @types/react-redux @types/react-router-dom (if using routing)
Enter fullscreen mode Exit fullscreen mode

Store Setup:

Create a store.ts file to define your Redux store:

`import { configureStore } from "@reduxjs/toolkit";
import authReducer from "../reducer/user/userAuthSlice"


export const store = configureStore({
    reducer: {
        auth: authReducer,
        }
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch`

Enter fullscreen mode Exit fullscreen mode

Configure Reducers

import { createSlice } from "@reduxjs/toolkit"

const initialState: any = {
    user: null,
    authToken : null
}
const userAuthSlice = createSlice({
    name : "auth",
    initialState,
    reducers: {
        saveUser(state :any, action:any){
            state.user = action.payload
        },
        saveToken(state :any, action:any){
            state.authToken = action.payload
        }
    }
},
)


export const { saveUser , saveToken } : any = userAuthSlice.actions


export default userAuthSlice.reducer
Enter fullscreen mode Exit fullscreen mode

Store the user Data and token in stores on successful login

      dispatch(saveUser(userCredential.user));
      dispatch(saveToken(userCredential.user.accessToken));
Enter fullscreen mode Exit fullscreen mode

To protect the application create a guard to only allow authenticated user to login

const Router = () => {
  const GUARD_ROUTE = (props: any) => {
    const { children } = props;
    const authToken: any = useSelector((state: any) => state.auth.authToken);
    if (authToken) {
      return <React.Fragment>{children}</React.Fragment>;
    } 
    if (!authToken) {
      toast.dismiss()
      toast.error("Please Login First");
    }
    return <Navigate to={RouteEnums.LANDING} />;
  };
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Landing />} />
        <Route path="/login" element={<Login />} />
        <Route path="/register" element={<Register />} />
        <Route path="/verification-sent" element={<VerificationNotice />} />
        <Route
          path="/home"
          element={
            <GUARD_ROUTE>
              <Home />
            </GUARD_ROUTE>
          }
        />
      </Routes>
    </BrowserRouter>
  );
};

export default Router;
Enter fullscreen mode Exit fullscreen mode

πŸŽ‰ Congratulations you have implemented Redux as well πŸŽ‰

Deployment

I used Vercel as it is the easiest and quickest option.

Steps

Push your app to a Git repository:

If you don't have one, create a new repository on GitHub, GitLab, or Bitbucket.
Push your local React app files to the remote repository.

Import your repository into Vercel:

  • Go to https://vercel.com/dashboard
  • Click "New Project" and select "From Git repository".
  • Choose your Git provider and authorize Vercel to access your repositories.
  • Select your repository and click "Import".
  • Vercel will automatically detect your React app and deploy it.

Image description

You'll receive a deployment URL upon completion.

Top comments (0)