Creating our react components
Since we are building a small application, we don't really need lots of components, in this project, we would be using four major components; Login, Signup, Signin, and Profile components.
Next, let's set up our routing using react-router-dom
which we installed earlier.
In our main.js
file, we should have the following code
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { ChakraProvider } from '@chakra-ui/react'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<ChakraProvider>
<App />
</ChakraProvider>
</React.StrictMode>,
)
Then in our "App.js" file, we should have the following code
import { Box } from '@chakra-ui/react'
import { useState } from 'react'
import { BrowserRouter as Router , Routes, Route, Navigate } from 'react-router-dom'
import Login from './components/Login'
import Profile from './components/Profile'
import Signup from './components/Signup'
function App() {
return (
<Box>
<Router>
<Routes>
<Route exact path='/' element={<Navigate replace to='/login' />} />
<Route exact path='/login' element={<Login />} />
<Route exact path='/signup' element={<Signup />} />
<Route exact path='/profile' element={<Profile />} />
</Routes>
</Router>
</Box>
)
}
export default App
The route has been set up in the app.js file, Navigate is used from react-router-dom to redirect users to the login page when the page loads.
Building the UI of the pages.
Now let's start building the UI of our components; the login, signup, and profile components before we start working on the functionalities.
For the Signup component, paste the following code
import React from 'react'
import {LoginCurve} from 'iconsax-react'
import {Box, FormControl, FormLabel, Button, Heading, Text, VStack, Input} from '@chakra-ui/react'
const Signup = () => {
return (
<Box mt='4'>
<VStack spacing={6}>
<Heading>Signup</Heading>
<Box maxW='400px'>
<FormControl>
<VStack spacing={4}>
<Box>
<FormLabel>Username</FormLabel>
<Input type='text' placeholder='Input your username' fontSize='14px' w='400px' />
</Box>
<Box>
<FormLabel>Bio</FormLabel>
<Input type='text' placeholder='Input your bio' fontSize='14px' w='400px' />
</Box>
<Box>
<FormLabel>Upload your avatar</FormLabel>
<Input type='file' w='400px' />
</Box>
<Box>
<FormLabel>Email</FormLabel>
<Input type='email' placeholder='Input your email' fontSize='14px' w='400px' />
</Box>
<Box>
<FormLabel>Password</FormLabel>
<Input type='password' placeholder='Input your password' fontSize='14px' w='400px' />
</Box>
<Box>
<Button type='submit' w='400px' colorScheme='teal'>Signup <LoginCurve style={{marginLeft: '5px'}} /></Button>
</Box>
</VStack>
</FormControl>
</Box>
</VStack>
</Box>
)
}
export default Signup
After pasting the code, we should have a UI that looks like the picture below
For the Login component, paste the following code
import React from 'react'
import { Box, FormControl, FormLabel, Button, Heading, Text, VStack, Input } from '@chakra-ui/react'
import { LoginCurve } from 'iconsax-react'
const Login = () => {
return (
<Box mt='10'>
<VStack spacing={6}>
<Heading>Login</Heading>
<Box maxW='400px'>
<FormControl>
<VStack spacing={4}>
<Box>
<FormLabel>Email</FormLabel>
<Input type='email' placeholder='Input your email' fontSize='14px' w='400px' />
</Box>
<Box>
<FormLabel>Password</FormLabel>
<Input type='password' placeholder='Input your password' fontSize='14px' w='400px' />
</Box>
<Box>
<Button type='submit' w='400px' colorScheme='teal'>Login <LoginCurve style={{ marginLeft: '5px' }} /></Button>
</Box>
</VStack>
</FormControl>
</Box>
</VStack>
</Box>
)
}
export default Login
The UI of the login component should look like the picture below after pasting the code.
Great! Our App is gradually getting into shape, now let's go ahead and create the UI of our profile page. Paste the code below for the profile page.
import React from 'react'
import {Box, VStack, Avatar, AvatarBadge, Text, Heading, Stack} from '@chakra-ui/react'
const Profile = () => {
return (
<Box maxW='400px' mt='16' bg='gray.100' p='5' borderRadius={5} boxShadow='md' display='flex' alignItems='center' justifyContent='center' mx='auto'>
<Box>
<Heading mb='5'>My Profile</Heading>
<Avatar bg='teal' size='xl' mb='5' display='block' mx='auto'>
<AvatarBadge boxSize='0.7em' bg='green.500' />
</Avatar>
<Stack spacing={4} fontWeight='semibold'>
<Text>Username: </Text>
<Text>Bio: </Text>
<Text>Email: </Text>
<Text>Status: </Text>
</Stack>
</Box>
</Box>
)
}
export default Profile
Our UI should look like the picture below after pasting the code
Great Job, we are done with building all the user interfaces of our app, now let's move to handling the form states, validation, and then adding the firebase authentication.
Handling the form states and validation
Let's start from the Signup page and then to the login page. We would start by storing the form data in states and then validating the form to avoid empty or invalid inputs and after that, we send the data to firebase.
import React, {useState} from 'react'
import { app } from '../../firebaseConfig'
import {LoginCurve} from 'iconsax-react'
import {Box, FormControl, FormLabel, Button, Heading, Text, VStack, Input, useToast} from '@chakra-ui/react'
import {getAuth, createUserWithEmailAndPassword, signOut} from 'firebase/auth'
import {addDoc, getFirestore, collection} from 'firebase/firestore'
import {getStorage, ref, uploadBytes, } from 'firebase/storage'
import { useNavigate } from 'react-router-dom'
const Signup = () => {
const [username, setUsername] = useState('');
const [bio, setBio] = useState('');
const [avatar, setAvatar] = useState(null);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
//initialise navigate from react router dom
const navigate = useNavigate();
//initalise toast from chakra ui
const toast = useToast();
//intialise firebase auth
const auth = getAuth();
//initialise firebase firesore database
const db = getFirestore();
//initialise firebase storage
const storage = getStorage();
const handleSubmit = async () => {
if(username && bio && avatar && email && password){
//create user with email and password
try {
const cred = await createUserWithEmailAndPassword(
auth,
email,
password
);
//upload the user avatar to firebase storage
const avatarRef = ref(storage, cred.user.uid);
await uploadBytes(avatarRef, avatar).then(() => {
console.log('avatar uploaded');
});
//collection reference
const colref = collection(db, 'user_data');
//add user's details as a document to the user_data collection
await addDoc(colref, {
user_name: username,
user_id: cred.user.uid,
user_bio: bio,
}).then(() => {
//set the inputs back to empty fields after the form has been submitted
// then redirects the user to the login page
setUsername('');
setEmail('');
setPassword('');
setBio('');
navigate("/login");
signOut(auth);
console.log("user signed out");
});
//show success toast if account creation was successful
toast({
title: "Account created.",
description: "Account created successfully!.",
status: "success",
duration: 3000,
isClosable: true,
variant: 'top-accent',
position: 'top-right',
});
} catch (error) {
// Handle any errors that occur during the function calls
toast({
title: "Error!",
description: error.message,
status: "success",
duration: 3000,
isClosable: true,
variant: "top-accent",
position: "top-right",
});
}
} else {
//show error toast if any of the fields is empty
toast({
title: "Error!",
description: "Please fill all fields correctly",
status: 'error',
duration: 2000,
isClosable: true,
variant: "left-accent",
position: "top",
});
}
}
return (
<Box mt='4'>
<VStack spacing={6}>
<Heading>Signup</Heading>
<Box maxW='400px'>
<FormControl>
<VStack spacing={4}>
<Box>
<FormLabel>Username</FormLabel>
<Input type='text' placeholder='Input your username' fontSize='14px' w='400px' value={username} onChange={(e) => setUsername(e.target.value)} />
</Box>
<Box>
<FormLabel>Bio</FormLabel>
<Input type='text' placeholder='Input your bio' fontSize='14px' w='400px' value={bio} onChange={(e) => setBio(e.target.value)} />
</Box>
<Box>
<FormLabel>Upload your avatar</FormLabel>
<Input type='file' w='400px' onChange={(e) => setAvatar(e.target.files[0])} />
</Box>
<Box>
<FormLabel>Email</FormLabel>
<Input type='email' placeholder='Input your email' fontSize='14px' w='400px' value={email} onChange={(e) => setEmail(e.target.value)} />
</Box>
<Box>
<FormLabel>Password</FormLabel>
<Input type='password' placeholder='Input your password' fontSize='14px' w='400px' value={password} onChange={(e) => setPassword(e.target.value)} />
</Box>
<Box>
<Button type='submit' w='400px' colorScheme='teal' onClick={handleSubmit}> Signup <LoginCurve style={{marginLeft: '5px'}} /> </Button>
</Box>
</VStack>
</FormControl>
</Box>
</VStack>
</Box>
)
}
export default Signup
Yeah! we are almost there, I know we might be scared by the amount of code above but it's nothing to be scared of, let’s go through it together while I explain it step by step.
As mentioned earlier, we would be using three out of the firebase services which are:
Firebase authentication for authenticating users.
Firestore database for storing users' data
Firebase storage for storing users' files and images.
First of all, we would start with the imports at the top of the component. If we could remember, we exported 'app' in our firebase config file, so the first thing we have to do before importing any of the firebase services in any file/component is to import the app, without doing this we would get an error.
The next thing we would do is to import the toast component from Chakra UI, this provides us with a toast that is customizable and also saves a lot of time.
After that we imported some functions from "firebase/auth", firebase has lots of services so we have to specify the kind of service we want, in this case, we want the auth service.
We imported getAuth()
which gives us the currently authenticated user and null if there's none.
We also imported createUserWithEmailAndPassword()
, just like its name, it allows us to create a user with email and password.
Then we also imported signOut()
which allows us to sign out the logged-in user.
Now let’s move to the firestore database service, we imported a couple of functions from it as well, which are:
-
getFirestore()
which returns a reference to a Firestore database. Firestore is a NoSQL, document-oriented database that stores data in collections and documents, which allows for easy querying, indexing, and scaling of data.
We can use the getFirestore()
method to access and manipulate data in our Firestore database. Once we have a reference to the Firestore database, we can use various methods provided by the Firestore SDK to create, read, update, and delete data.
-
Another one is
collection()
. In Firestore, a collection is a group of documents that share a common path. we can think of a collection as a container for documents that have similar properties or belong to the same type of entity.For example, we might have a collection called "users" that contains documents representing individual users in our application.
The
collection()
method in the Firebase Firestore SDK is used to retrieve a reference to a specific collection within our database. Once we have a reference to a collection, we can use various methods to add, read, update, and delete documents in the collection.The last one from firebase firestore is the
addDoc()
function, it is used to add a new document to a collection with an automatically generated id, it takes in the collection reference as an argument and also the document to be added.
Up next is the firebase storage and we imported some functions from it as well.
-
The first one is
getStorage()
, It is a method in the Firebase Storage SDK that returns a reference to a Firebase Storage instance, which is a cloud-based file storage solution provided by Firebase.Firebase Storage allows us to store and retrieve user-generated content such as images, videos, and other files, and it integrates seamlessly with other Firebase services such as Authentication and Firestore.
Once we have a reference to a Firebase Storage instance, we can use various methods provided by the Storage SDK to upload, download, and delete files, as well as manage storage security and access.
-
The second one is
ref()
. it can be used to get a reference to either a file or a directory within the Firebase Storage bucket.If the specified location does not exist, Firebase Storage will create it automatically when a file is uploaded to that location.
-
Last but not least is the
uploadBytes()
, it is a method in the Firebase Storage SDK that uploads a Uint8Array, ArrayBuffer, Blob, or a File to a specified location in a Firebase Storage bucket.This method allows us to upload binary data such as images, videos, or other files to Firebase Storage.
The next thing we imported was the useNavigate
from react-router-dom.
it is used to redirect a user to a specific route after an action has been performed.
And that's all for the imports, the next thing we did was that we created a state for each of the input fields and then attach them to the inputs respectively.
Up next is initializing all the functions imported at the top.
In the handleSubmit function, let’s not forget its an async function, we have to check if all the input fields are empty or not, if they are empty, an error toast will be displayed telling the user to fill the inputs, therefore the form won't submit.
If the inputs are filled correctly, a user will be created with email and password through the createUserWithEmailAndPassword()
function.
Immediately after the user has been created, we have access to the user object through the cred variable, and from the user object, we can access the user's id which was generated by firebase auth.
The next thing is to upload the user's avatar to firebase storage but before then we have to create a reference for the avatar by using the ref()
function which takes in the storage and the image name as an argument.
Here we are using the user's id user.uid
as the name of the image for two reasons.
- The first reason is to avoid name conflicts, because some images may have the same name
- The second reason is that anytime we want to retrieve the image, it will be very easy to track and retrieve the logged-in user's avatar.
After that, we use the uploadBytes()
function to upload the image to firebase storage. it takes two arguments, the avatar reference and the avatar variable itself.
Next, we have to add the user's username and bio to the firestore database by using the addDoc()
function, but before that, we have to create a collection reference, i.e where we want to add the document to.
we do this by using the collection()
function, it takes in two arguments which are the “db” and the name of the collection. Don't worry if we don't have any collections created, firebase will create one for us automatically.
After that, we can now use addDoc()
function and then pass the colref and the data we want to add in form of an object.
The next thing we did was to set the states of the input fields back to their empty states, redirect the user to the login page and then log the user out by using the signOut()
function.
Then a success toast shows that the account has been created successfully, or an error toast if anything goes wrong.
To test things out, we head over to our firebase console, and check all three sections: Authentication, firestore database, and storage, we would see the recently signed-up user's details just like we have them in the pictures below.
Firebase authenticated users
User Information stored in firestore database collection
Users' avatars that have been stored in firebase storage
And that’s going to be the end of this series, stay tuned for the concluding part.
In case you missed the first part of the series, you can read it here
You can also read the concluding part of the series here.
Top comments (0)