This post is a walkthrough on how to configure and develop a React application written in TypeScript, using a Firebase backend service.
We will touch upon some basic setup procedures for integrating Firebase into a React App, focusing on features such as registration, login, and authorization.
There is a link to the repository in the TL;DR section. This guide is intended as a setup for a meetup on how to use React and Firebase in a sample app. Essentially, it's a call to action to inspire you to create your own apps using React and Firebase, or even to explore other frameworks besides React (please note that we will not be discussing other frameworks in this blog post).
I hope you find building this application enjoyable and that it serves as a stepping stone for you to create your own apps using Firebase in the future.
What We Will Need:
- Node version of at least LTS which is currently v16.18.0
- Your favorite text editor (I will be using VSCode)
- A Firebase account - we will set this up later, so relax and don't worry about it for now.
- Also make sure Java is installed on your machine; we need it to make the firebase emulator work.
- The latest version of Vite, which we will use to create a React project. Installing this will be covered later.
What Basic Knowledge This Blog Expects You to Have:
- Some basic command-line knowledge, including creating directories, changing directories, and running script commands.
- Some basic React and JS/TS knowledge is welcome, but we will cover some of the basics and provide explanations. Minimal knowledge is acceptable too. As long as you know what a frontend framework is and have some JavaScript knowledge, you will be fine.
- We will be using TypeScript but won't be explaining the typing, so some knowledge is a nice-to-have but not necessary to follow along.
- Some basic knowledge of Git will come in handy because some concepts will be mentioned but not explained.
Don't worry, this blog will explain how to set up Vite and create a Firebase project. Along the way, more dependencies will be added, and we will explain why we need them.
TL;DR
For those of you who just want to check out the code:
The project can be found here.
And if you want to go step by step of creating the project just stick around and keep reading.
So lets get cracking!! 🐙
Setting up Firebase
Go to this link and register for a Firebase account.
Once you've completed the registration, head to the Firebase console and click on the Add Project tile. You'll go through some steps to create a project.
In the first step, give your project a unique name. Please note that a project name is different from an app name. Later in the process, you'll provide a name for your app.
Next, you'll be prompted to decide if you want to use Google Analytics. We won't be discussing the Google Analytics feature in this blog, but feel free to select it and explore on your own. Firebase recommends its use, so that might be a good choice.
In the following step, select the Default Account for Firebase option and press Create Project. Once your project is created, you'll be redirected to your project console overview page.
For now, we're done with Firebase. We'll return to it after setting up a React project in the Configure Firebase in the React Project part of this blog.
Creating a Fresh React Project with Vite
This blog won't delve deeply into Vite, but we will touch on how to create a boilerplate React project. This will save us a ton of time, allowing us to focus on building the good stuff.
A little bit about Vite: as you can probably guess, it's a tool that quickly generates boilerplate projects for most popular frontend frameworks. It's pretty cool, to say the least. If you're interested in learning more, check out the Vite webpage.
First, let's install Vite globally, which will make creating boilerplate projects a breeze. Run the following command in your terminal to install Vite:
npm i -g vite
The -g
flag ensures that Vite is installed globally (making it usable by all users on your computer).
Next, navigate to the folder where you'd like to store the project and run the following command. Make sure to replace <project-name>
with the name you've chosen for your project.
I've chosen to name my project firebase_basics_with_react, but remember to replace <project-name>
in the command with your own project name.
npm init vite@latest firebase_basics_with_react -- --template react-ts
Once this process is completed, Vite will provide a prompt in the command line interface (CLI) that looks something like this:
Done. Now run:
cd firebase_basics_with_react
npm install
npm run dev
Copy the provided command and paste it into your command line.
Now, navigate to http://localhost:5173/
in your browser, and your Vite React project in TypeScript should be all set up.
It's time to open your favorite text editor and start coding!
Clean Up the React Project
Open the project in your text editor. We're going to remove some of the boilerplate to make the app feel more like our own. Navigate to the root folder of the project and open the src
folder.
The contents should look a little like this:
src
> assets
App.css
App.tsx
index.css
main.tsx
vite-env.d.ts
First, clear all content in the App.tsx
file and leave the rest for now. Everything will be updated in due time.
Next, create a firebase
folder inside the src
folder and add an index.ts
file inside it.
Configure Firebase in the React Project
Now that you have a Firebase Project and a fresh new React boilerplate project, it's time to combine the two and start our app-building adventure. Return to the Firebase console for your brand new project.
Select the button marked with closing brackets </>
. This will take us to the section where we can create a Web Application.
Next, register your app and choose a name for it. For now, leave the hosting checkbox unchecked. We'll discuss this option later. Click the "Register App" button to proceed.
The next step is to add the Firebase SDK to our project. We'll use the index.ts
file in the Firebase folder we created earlier for this purpose.
We'll be using NPM for this, so we can follow the steps presented on the Firebase webpage. Head over to the command line, navigate to the root folder of your React app, and run the npm install command:
npm install firebase
Next, create a file called .env
at the root of your project and define the following environment variables:
VITE_API_KEY=
VITE_AUTH_DOMAIN=
VITE_PROJECT_ID=
VITE_STORAGE_BUCKET=
VITE_MESSAGE_SENDER_ID=
VITE_APP_ID=
VITE_MEASUREMENT_ID=
If you look at the Firebase console in your browser, beneath the npm install firebase
script, you'll find an example of how to initialize the Firebase app inside the React app.
The code snippet should look something like this:
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: "YOUR KEY",
authDomain: "YOUR DOMAIN",
projectId: "YOUR PROJECT ID",
storageBucket: "YOUR STORAGE BUCKET",
messagingSenderId: "YOUR MESSAGING SENDER ID",
appId: "YOUR APP ID",
measurementId: "YOUR MEASUREMENT ID"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);
The values in the firebaseConfig
object can be pasted into the corresponding variables in the .env
file. Please note that the .env
file should be added to the .gitignore
file of your project if you plan to use a Git repository for your project. Never push keys to an open source repo! In fact, never push keys to any repo for the sake of security!
Now, let's insert the initialization code into our project. This code belongs in the index.ts
file in the Firebase folder.
import { initializeApp } from 'firebase/app';
// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: import.meta.env.VITE_API_KEY,
authDomain: import.meta.env.VITE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_PROJECT_ID,
storageBucket: import.meta.env.VITE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_MESSAGE_SENDER_ID,
appId: import.meta.env.VITE_APP_ID,
measurementId: import.meta.env.VITE_MEASUREMENT_ID,
};
const FireBaseApp = initializeApp(firebaseConfig);
export default FireBaseApp;
Adding Routing
In this section, we'll focus on setting up routing, allowing us to navigate through different pages. The end result of this blog/app will include a sign-up and login page with a password reset email feature. When the user logs in, they'll be redirected to a homepage. That's the goal we're working towards, with some stylish embellishments provided by Tailwind.
Let's start by adding React Router to the project.
If you don't already have a terminal session open, start one now, navigate to your React project folder, and install the React Router package as a dependency by running this npm install command:
npm install react-router-dom
Once the installation is complete, let's head over to the main.tsx
file where we'll modify the code. Currently, we have only one page with some Vite content running, but we want more than one page in our app. React Router can be configured in two ways:
We use
createBrowserRouter
inside the React render function, which takes an array of objects as an argument. These objects hold different paths and the corresponding components that render content when the route is requested by the user. ThecreateBrowserRouter
will then be passed to theRouterProvider
component inside the React render function.The alternative method is using
createRoutesFromElements
and passing this as an argument to thecreateBrowserRouter
function. In this approach, you don't pass routes as objects that represent all routes in your app; instead, you can use React components.
Enough talking, here is the code snippet for the routing. Just paste it into the main.tsx
file:
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import "./index.css";
import App from "./App";
const router = createBrowserRouter([
{
path: "/",
element: <App />,
},
]);
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
Let's proceed by creating two additional folders in our project: pages
and components
. Create both folders inside the src
folder and include an index.ts
file in each. We'll utilize Barrels for importing components into another file. This approach will consolidate our imports into a single file from where we'll import pages and components into other parts of the application.
Your project folder structure should now resemble something like this:
src
> assets
> components
index.ts
> pages
index.ts
App.css
App.tsx
index.css
main.tsx
vite-env.d.ts
Our next step involves moving the App.tsx
file into the pages
folder and updating the index.tsx
file inside the pages
folder as follows:
export { App } from "./App";
Don't forget to update the import of the App
component inside the main.tsx
file. Change it to import App from "./pages/App";
.
We'll add a very basic component inside the App.tsx
file to verify that everything works up to this point:
const App = () => <div>Hello APP!</div>;
export default App;
The setup should now render Hello APP!
in the browser. It might appear somewhat awkwardly positioned midway in the browser, but we'll address that in the next section.
Adding Styling with Tailwind
Now it's time to set up the basics for styling. For this project, we'll be using Tailwind. Tailwind offers fast and easy styling options, which allows us to bypass the need for explaining a lot of CSS details that are not within the scope of this blog post. Tailwind provides classes that carry the styling for us, so by adding these classes to the HTML elements, we can update the styling without directly using CSS, as Tailwind has already taken care of that for us.
Let's get it set up and make this app shine! 💎✨🪩
Tailwind docs provide a comprehensive guide on setting up Tailwind, but we'll need to adjust the procedure a bit in our case.
This is due to Tailwind's dependency on PostCSS. PostCSS serves as a build system in Tailwind to generate CSS from its unique custom syntax. Additionally, we'll need Autoprefixer to ensure the CSS it generates is compatible with as many browsers as possible.
Armed with this knowledge, we need to do a bit more work, which involves installing a few extra packages and applying some configuration. So let's get to it!
npm install -D tailwindcss postcss autoprefixer
Next run:
npx tailwindcss init
It will add a tailwind.config.js
file.
Update the file so it looks like this:
/** @type {import('tailwindcss').Config} */
export default {
content: [ "./index.html", "./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
}
Also create a postcss.config.js
file and add this:
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Next step is updating the index.css
file by pasting this code:
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
background-color: #f0fdfa;
}
When we update the App.tsx
file to this:
const App = () => (
<div className="min-h-screen flex justify-center items-center">
<h1 className="text-3xl font-bold text-blue-600">
Install & Setup Vite + React + Typescript + Tailwind CSS 3
</h1>
</div>
);
export default App;
The background should be slightly green, with text centered in the middle of the screen and displayed in a bold, sans-serif font, as shown below:
Now this is done lets continue on the app itself.
Setting up Firebase Authentication
Here's a brief overview of the user experience we're aiming for in our app once it's complete.
When a user visits the app, they will be presented with a signup form that includes three input fields: one for email, one for username, and one for password. This form will also feature a signup button and a link for logging in. Clicking this link will change the signup form to a login form, which will only require email and password fields as it's assumed that the user already has an account, thus eliminating the need for a username.
Once a user signs up or logs in with a valid username and password, they'll be redirected to a user homepage, where they'll be greeted with their username. Sounds super awesome, right? 🤩
First, let's enable authentication in Firebase for the app.
Go to the project overview page of your Firebase project on the web and select "Authentication". Press the "Get Started" button.
For now, we will only use email and password authentication, so select the "Email/Password" option. Enable both "Email/Password" and "Email Link" options, then click "Save".
Now, let's get coding!
Create an AuthForm
folder component inside the components
folder. Within that AuthForm
folder, create an index.tsx
file and paste the following code into it:
import {
getAuth,
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
sendPasswordResetEmail,
updateProfile,
} from "firebase/auth";
import { FormEvent, MouseEvent, useState } from "react";
import { useNavigate } from "react-router-dom";
import FireBaseApp from "../../firebase";
import { FirebaseError } from "firebase/app";
export const AuthForm = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [displayName, setDisplayName] = useState("");
const [signUp, setSignUp] = useState(true);
const [error, setError] = useState<string | undefined>();
const auth = getAuth(FireBaseApp);
const navigate = useNavigate();
const handleSubmit = async (event: FormEvent) => {
event.preventDefault();
if (signUp) {
try {
await createUserWithEmailAndPassword(auth, email, password);
const user = auth.currentUser;
if (user) {
updateProfile(user, {
displayName,
});
}
navigate("/dashboard");
} catch (error) {
console.log(error);
}
}
try {
await signInWithEmailAndPassword(auth, email, password);
navigate("/dashboard");
} catch (error: unknown) {
if (error instanceof FirebaseError) {
setError(error.code);
} else {
setError("An error occurred");
}
}
};
const toggleSignUp = (event: MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
setSignUp(!signUp);
};
const resetPassword = async () => {
try {
sendPasswordResetEmail(auth, email);
} catch (error) {
console.log(error);
}
};
return (
<form
onSubmit={(event) => handleSubmit(event)}
className="bg-white p-6 rounded-lg shadow-xl"
>
<h1 className="text-center">{signUp ? "Sign Up" : "Sign In"}</h1>
{signUp && (
<>
<label
className="block text-gray-700 font-medium mb-2 text-left"
htmlFor="username"
>
Username
</label>
<input
type="username"
name="username"
value={displayName}
onChange={(event) => setDisplayName(event.target.value)}
className="w-full p-3 border border-gray-400 rounded-lg outline-teal-500"
id="username"
required
/>
</>
)}
<div className="mb-4">
<label
className="block text-gray-700 font-medium mb-2 text-left"
htmlFor="email"
>
Email
</label>
<input
type="email"
name="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
className="w-full p-3 border border-gray-400 rounded-lg outline-teal-500"
id="email"
required
/>
</div>
<div className="mb-6">
<label
className="block text-gray-700 font-medium mb-2 text-left"
htmlFor="password"
>
Password
</label>
<input
type="password"
name="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
className="w-full p-3 border border-gray-400 rounded-lg outline-teal-500"
id="password"
required
/>
</div>
<p className="text-red-400">{error && error}</p>
<button
type="submit"
className="bg-teal-500 transition duration-500 hover:bg-teal-600 text-white font-medium w-full p-3 rounded-lg"
>
{signUp ? "Register" : "Login"}
</button>
<div className="flex">
<div className="flex-start ">
<button
className="mr-4 transition duration-500 hover:underline"
onClick={(event) => toggleSignUp(event)}
>
{signUp ? "Sign In" : "Sign Up"}
</button>
<button
className="transition duration-500 hover:underline"
onClick={() => resetPassword()}
>
Forgot Password
</button>
</div>
</div>
</form>
);
};
We also need a Barrel file to export the components from so inside the components folder create an index.ts for and paste this code in:
export { AuthForm} from './AuthForm'
Let's zoom a bit in on some of the imports in this file so we can see what awesome functionality firebase/auth has for us that we use in the app.
import {
getAuth,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
sendPasswordResetEmail,
updateProfile
} from 'firebase/auth';
getAuth we use on line 19 of the file, we pass the FireBaseApp
as an argument. The FireBaseApp
was created in the Firebase configuration file in the firebase folder of the app. So we pass the object with all the credentials of the app as an argument and store it inside the variable auth
. This auth
variable we need to call all the other functions we import from firebase/auth
. So let's move on to the next one.
__createUserWithEmailAndPassword__
- Well, I think you can guess what this callback function does. We use it in the submit handler of the signup part of the form. We pass the auth
variable and the user email and password from the form input as arguments to persist this user in our Firebase Authentication part of the app. The next thing we do is add a display name by calling the __updateProfile__
function and pass the user input field value to it.
The __signInWithEmailAndPassword__
is used in the sign-in form to get the user from our firebase authorization database. We pass the auth
variable and the email and password of the user to the function.
And Firebase also provides a nice password reset function which we use inside a resetPassword function on line 56. We just need to pass the auth
variable and the users email address. Please note that the email could end up in the spam folder.
const auth = getAuth(FireBaseApp);
Now add the form to the App.tsx file.
Your App.tsx file should look like this:
import { AuthForm } from "../components";
const App = () => {
return (
<div className="grid place-items-center">
<div className="w-3/6 max-md:w-full mt-5">
<AuthForm />
</div>
</div>
);
};
export default App;
And also update the main.tsx
to add the dashboard route.
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import "./index.css";
import App from "./pages/App";
import Dashboard from "./pages/Dashboard";
const router = createBrowserRouter([
{
path: "/",
element: <App />,
},
{
path: "/dashboard",
element: <Dashboard />,
},
]);
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
Let's add a new page and create a route for it. Inside the pages
folder, create a new file called Dashboard.tsx
, and paste the following code:
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { getAuth } from "firebase/auth";
import { Navigation } from "../components";
export const Dashboard = () => {
const auth = getAuth();
const user = auth.currentUser;
const navigation = useNavigate();
useEffect(() => {
if (user == null) {
navigation("/");
}
}, [user, navigation]);
return (
<>
<Navigation pageName="Dashboard" />
<div className="container mx-auto m-5">
<div className="grid grid-cols-1 gap-4">
<div className="bg-white p-6 shadow-md mb-5 mt-5 transition duration-500 hover:shadow-xl rounded">
<h2>
Welcome:{" "}
<span className="text-lg font-bold">{user?.displayName}</span>
</h2>
<hr className="h-px my-8 bg-gray-200 border-0 dark:bg-gray-700"></hr>
<h3>You are logged IN!!🔥🔥🔥🔥🔥</h3>
</div>
</div>
</div>
</>
);
};
export default Dashboard;
We will also need a Navigation component.
Let's create a Navigation folder with an index.tsx file in it in the components folder and paste the following code:
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { getAuth, signOut } from "firebase/auth";
export const Navigation = ({ pageName }: { pageName: string }) => {
const [signedIn, setSignedIn] = useState(true);
const navigate = useNavigate();
const auth = getAuth();
const clickHandler = () => {
setSignedIn(!signedIn);
signOut(auth)
.then(() => {
navigate("/");
})
.catch((error) => {
console.log(error);
});
};
return (
<nav className="bg-teal-300 p-4">
<div className="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8 flex items-center justify-between">
<div className="flex items-center">
<Link to="/dashboard" className="text-white text-2xl font-bold">
Tada!
</Link>
</div>
<div className="ml-6 space-x-4 text-center text-white text-2xl font-bold">
{pageName}
</div>
<div className="flex items-center">
<button
className="bg-teal-500 transition duration-100 hover:bg-teal-600 text-white font-bold py-2 px-4 rounded"
onClick={() => clickHandler()}
>
{signedIn ? "Sign out" : "Sign in"}
</button>
</div>
</div>
</nav>
);
};
Also add the navigation component to the components folder barrel file so the index.ts
inside the components folder.
make sure it looks like this:
export { AuthForm} from './AuthForm'
export { Navigation } from "./Navigation";
During the hackathon we will use a slightly different Dashboard page look and will be adjusted to match a Todo list app.
Using the Firebase Emulator
Let's kick this one off by explaining what it is and why we need it. The Firebase Emulator allows us to develop locally, using our own local Firebase to test and develop new features. This means we don't interfere with our actual Firebase project. When we're happy with our new feature, we can push and deploy the code so in production, we actually use our live Firebase project. This helps us avoid polluting our Firebase project with testing data. We won't delve deeper into this topic in this blog but it will be covered during the workshop. We will need the Firebase Emulator when we start building collections and documents in the app.
Firebase Hosting
Let's get this app on the web!
We need the Firebase toolkit to set up hosting. You'll also need it to run the emulator, but we can leave that bit for now.
To install the Firebase toolkit, we will do a global install like so:
npm install -g firebase-tools
Once this is done, we need to initialize the app in order for the toolkit to work. In this process, we will connect the React app to the Firebase project on the web.
In the root of the project, run:
firebase init
now you will see a CLI menu in this menu just select
Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub
Action deploys
for now.
Now you will see a CLI menu. In this menu, just select Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys for now.
You can select it by using the arrow down key and select it by pressing the space bar. After this is done, press enter to continue to the next step and that is selecting the Firebase project.
Choose the option to select a project and pick your project by selecting it.
You can select a GitHub project, but it's optional and we will just deploy, so I selected no for now.
The next step is selecting a server where your app will be hosted, just choose one that is near you.
Now that the configuration is done, we are ready to deploy and we can do that by running this command.
firebase deploy
Now you should have deployed your very own app on the web!!
Final words
Well done! You have successfully created a React App and added a Firebase project to it.
I hope you enjoyed this blog. I will probably add a follow-up after the meetup with more in-depth things explained, such how to use the Firestore database and create collections, and update and delete documents from it. So stay tuned and keep on coding! 🧑💻
Top comments (0)