Authentication in React Applications using Firebase and its services. Part 3
Ayanwumi Abdulroheem Tunde
Posted on April 9, 2023
Logging the user in
Now that we are done signing the user up, and have signed the user out, the next thing is to log the user back in. Back to our Login component, let's see how we can achieve this with firebase.
But first, we have to add states to our inputs, then start working on implementing the functionality.
import React, {useState} from 'react'
import { Box, FormControl, FormLabel, Button, Heading, Text, VStack, Input, useToast } from '@chakra-ui/react'
import { LoginCurve } from 'iconsax-react'
import { app } from '../../firebaseConfig'
import {getAuth, signInWithEmailAndPassword} from 'firebase/auth'
import { useNavigate } from 'react-router-dom'
const Login = () => {
//initialise firebase auth
const auth = getAuth();
//initialise toast component from chakra Ui
const toast = useToast();
//initialise navigate from react-router-dom
const navigate = useNavigate();
//states for each of the imputs
const [email, setEmail] = useState('')
const [password, setPassword] = useState('');
const handleClick = async () => {
if(email && password){
try {
await signInWithEmailAndPassword(auth, email, password).then((cred) => {
toast({
title: "Login Successful.",
description: "You have been logged in successfully!.",
status: "success",
duration: 3000,
isClosable: true,
variant: 'left-accent',
position: 'top-right',
});
setEmail('');
setPassword('');
navigate('/profile')
})
} catch (error) {
toast({
title: "Error!",
description: error.message,
status: "error",
duration: 3000,
isClosable: true,
variant: 'left-accent',
position: 'top',
});
}
}else{
toast({
title: "Error!",
description: "Please fill in your details.",
status: "error",
duration: 3000,
isClosable: true,
variant: 'left-accent',
position: 'top',
});
}
}
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' 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={handleClick}>Login <LoginCurve style={{ marginLeft: '5px' }} /></Button>
</Box>
</VStack>
</FormControl>
</Box>
</VStack>
</Box>
)
}
export default Login
The code above is the final code for the Login component after adding states to our inputs and writing the functionality. Copy and paste the code in the code editor while we go through the codebase step by step.
Let's start with the imports, I believe we are now familiar with almost all the imports except for the signInWithEmailAndPassword()
function. What this function does is that it allows us to sign a user in with the email and password that have been provided earlier by the user during signup. Then we called the function in the handleSubmit()
function. If everything goes well, the user will be logged in and then redirected to the profile page. And if anything goes wrong the error toast will be displayed with the error message.
Retrieving the user's data from the firebase auth, firestore database, and firebase storage
Now we have been able to sign users in with their email and password, and we have been able to redirect them to the profile page so that each user can view his/her profile but nothing is actually showing on the profile page, how do we fix this?
We can do this in 3 steps.
- The first thing is to get the currently logged-in user and from there, we can get the user's email and the date the account was created.
- The second step is to get the user's avatar from firebase storage.
- The third and final step is to get the user's username and bio from firestore database.
import React, {useState, useEffect} from 'react'
import {Box, Avatar, AvatarBadge, Text, Heading, Stack, Button, useToast} from '@chakra-ui/react'
import {getAuth, signOut } from 'firebase/auth'
import { getFirestore, query, where, getDocs, collection } from 'firebase/firestore'
import { getStorage, ref, getDownloadURL } from 'firebase/storage'
import { Link, useNavigate } from 'react-router-dom'
import { app } from '../../firebaseConfig'
const Profile = () => {
const [currentUser, setCurrentUser] = useState(null);
const [userDetails, setUserDetails] = useState('')
const [avatar, setAvatar] = useState('');
const auth = getAuth();
const db = getFirestore();
const storage = getStorage();
const userRef = collection(db, 'user_data');
const navigate = useNavigate();
const toast = useToast();
const signout = () => {
signOut(auth).then(() => {
navigate('/login');
toast({
title: "Logout Successful.",
description: "You have been logged out successfully!.",
status: "success",
duration: 3000,
isClosable: true,
variant: 'left-accent',
position: 'top-right',
});
})
}
const getCurrentUser = async () => {
try {
auth.onAuthStateChanged((user) => {
setCurrentUser(user);
const q = query(userRef, where('user_id', '==', user.uid));
const avatarRef = ref(storage, user.uid);
if(user){
getDownloadURL(avatarRef).then((url) => {
setAvatar(url);
});
getDocs(q).then(async (snapshot) => {
let user_data = [];
snapshot.docs.map((item) => {
user_data.push({ ...item.data(), id: item.id });
return setUserDetails(user_data);
})
});
}
})
} catch (error) {
toast({
title: "Error!.",
description: error.message,
status: "error",
duration: 3000,
isClosable: true,
variant: 'left-accent',
position: 'top-right',
});
}
};
useEffect(() => {
getCurrentUser()
}, []);
const newDate = currentUser?.metadata.creationTime
const date = new Date(newDate).toDateString();
return (
<>
{currentUser ?
<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' src={avatar}>
<AvatarBadge boxSize='0.7em' bg='green.500' />
</Avatar>
<Stack spacing={4} fontWeight='semibold'>
<Text>Username: {userDetails[0]?.user_name}</Text>
<Text>Bio: {userDetails[0]?.user_bio}</Text>
<Text>Email: {currentUser?.email}</Text>
<Text>Date Joined: {date}</Text>
</Stack>
<Box textAlign='center' mt='7'>
<Button colorScheme='teal' onClick={signout}>Logout</Button>
</Box>
</Box>
</Box>
:
<Box mx='auto' mt='16'>
<Heading fontWeight='semibold' fontSize={25} mb='3'>OOPS!</Heading>
<Text mb='3'>You are not authorised to view this page, please login to get acces</Text>
<Link to='/login'>
<Button>Login</Button>
</Link>
</Box>}
</>
)
}
export default Profile
We should have a UI that looks like the picture below after pasting the code.
And that's it, the code above is for the profile page, once again it might look strange but let's go through it together.
Like I always do, I will start from the imports at the top, I know we are familiar with most of the things I imported so I will just go over the ones I haven't mentioned before.
So let's start with the firebase storage.
-
We imported
getDownloadUrl()
from firebase storage. what it is used for is to get an accessible URL of the image we uploaded to firebase storage because by default, immediately after we uploaded the image, we have given a name to it but we can only access the image in our firebase console, and not anywhere else.- The next thing is the firebase firestore, we imported the
query()
function andwhere()
function which would be used to query the firestore database based on some conditions. A concept in firestore database that I won't go deep into is "firestore queries". What are firestore queries?
- The next thing is the firebase firestore, we imported the
Firestore queries are a way to retrieve data from a Firestore database in a specific way.
Firestore is a NoSQL document database that stores data in documents and collections, and queries can be used to retrieve documents from collections based on specified conditions.
Firestore queries can filter documents based on specific conditions, sort documents based on one or more fields, limit the number of documents returned, and retrieve only specific fields from each document.
Queries can also be combined to create more complex queries.
Firestore supports two types of queries: simple queries and complex queries. Simple queries are queries that use a single filter condition, while complex queries use multiple filter conditions, or multiple sorting or limiting conditions.
Firestore queries can be executed on the client side, such as in mobile or web applications, or on the server side using Cloud Functions or other server-side technologies.
So I believe we have a little knowledge about firestore queries and how they work now, so let's go back to our code.
Another thing we imported was the getDocs()
function, which is used to get all the documents in a firestore collection.
The next thing we did was create states for the avatar, the user details, and the current user. After that, I initialized all the functions we imported from firebase.
Up next is the signout function we created and attached to a button to sign users out when it is clicked and then redirect them to the login page.
The next and most important thing is to get the user's data from firebase.
Once we initialized the getAuth()
function, we have access to the onAuthStateChanged()
function which helps us to keep track of the authentication state of the user either currently logged in or not, and from that, we have access to the "user" object which gives us all the details about the currently logged in user and then we pass the user object to the setCurrentUser()
function.
The next thing is to use the firestore queries to get the currently logged-in user's details. The query function takes in two arguments, the userRef
and the where()
function, in the where function we pass in our query condition and it goes thus.
We want firebase to go through all the documents in the collection and get us the document that has its user_id
key equal to the currently logged-in user's id.
After that, we created a reference for our avatar. if we can still remember, what we did earlier was that immediately after the user signs up and uploads his picture, we changed the name of the image to the user's id.
The next code block checks if a user is present, and if it is, it should get us the download URL of the user's avatar.
After that, we used the getDocs()
function and then pass in the query to get the user details. We then use the .then()
method to get the snapshot, We created an empty array and set it to the user_data variable.
After getting the snapshot, we automatically have access to the docs()
method, we then looped through it and after doing that we pushed the data into the empty array, then pass the user_data
array into the setUserDetails()
function.
If we noticed in the snapshot.docs()
method we pushed item.data()
into the array and that's because the data we want that is coming from firebase is located in the data()
function. We can check this out by console logging the item console.log(item)
.
We then called the function in a useEffect()
.
Wow! guys we just built a simple full-stack application with firebase, that's so amazing, thanks for following me throughout the project, and also thanks for reading this article, I hope to release another article soon.
Conclusion
Firebase can be used to build full-stack applications for both web and mobile, in this article, we demonstrated how to use different firebase features and services, this is just a simple project and firebase has lots of features that can be used to build more complex applications. Check out firebase docs to learn more.
In case you missed the first and second parts of the series, you can read them by using the links below.
Posted on April 9, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
February 22, 2023