Firebase 101

Getting started with firebase


Terminology

Out of many things the firebase has to offer in this blogs, I will talk about two of it's usecases,

  1. Authentication (Which comes out of box)
  2. Database (Using firestore)

Setting up firebase

firebase.config.js
import { initializeApp } from 'firebase/app'
import { getFirestore } from 'firebase/firestore'
 
const firebaseConfig = {
  apiKey: ADD_KEY,
  authDomain: ADD_KEY,
  projectId: ADD_KEY,
  storageBucket: ADD_KEY,
  messagingSenderId: ADD_KEY,
  appId: ADD_KEY,
}
const app = initializeApp(firebaseConfig)
// intialize the firestore database
export const db = getFirestore()

CRUD in Firebase

Read

There are 2 parts to getting data in firestore,

1st : Creating a reference of the document or collection (It is like creating a connection to the database, which will make sure that you get the latest updated data)

2nd : Fetching the data (snapshot)

Getting all the documents from a collection

// Create a reference of the users collection,
// collection is function in the firebase/firestore module
const usersRef = collection(db, 'users')
const usersSnap = await getDocs(userRef)
// Now the usersSnap contains an array of objects with the user data and id
// usersSnap = {id, data()}
 
// Extract all the users
const users = []
usersSnap.forEach((doc) => {
  users.push({
    id: doc.id,
    data: doc.data(),
  })
})

Getting / Reading selected documents using query

const usersRef = collection(db, 'users')
const q = query(usersRef, where('name', ==, 'adesh'))
const usersSnap = await getDocs(q)
const users = []
usersSnap.forEach(doc => {
	users.push({
		id : doc.id,
        data : doc.data()
    })
})

Can also add orderBy or limit :

const q = query(
  listingsRef,
  where('offer', '==', true),
  orderBy('timestamp', 'desc'),
  limit(10)
)

Reading a single document

const userRef = doc(db, 'users', user.id)
const userSnap = await getDoc(userRef)
 
if (docSnap.exists()) {
  console.log(docSnap.data())
  // setting the user state
  setUser(docSnap.data())
}

Create

There are 2 function to create and add new documents in the collection : addDoc & setDoc

Use setDoc when you want to specify the id for the document yourself, Use addDoc when you want firebase to generate an id for you.

setDoc

// To get reference of single document, use doc to get reference of entire collection use collection
 
const userRef = doc(db, 'users', user.uid)
// setDoc takes the userRef and the data object to add for it
await setDoc(userRef, { name: 'adesh', age: 21 })

addDoc

const usersRef = collection(db, 'users')
await addDoc(userRef, { name: 'adesh', age: 21 })

Notice that we have used the collection ref in the addDoc, since we wanted an auto-generated id

Update

const updateUser = async (id, age) => {
  const userDoc = doc(db, 'users', id)
  // add only the updated field, rest will remain same
  const newFields = { age: age }
  await updateDoc(userDoc, newFields)
}

Delete

const deleteUser = async (id) => {
  const userDoc = doc(db, 'users', id)
  await deleteDoc(userDoc)
}

Sign In / Sign Up with Email and Password

Use this docs https://firebase.google.com/docs/auth/web/password-auth, its really simple.

SignIn.jsx
const onSubmit = async (e) => {
  e.preventDefault()
 
  try {
    const auth = getAuth()
 
    const userCredential = await signInWithEmailAndPassword(
      auth,
      email,
      password
    )
 
    if (userCredential.user) {
      navigate('/')
    }
  } catch (error) {
    // console.log(error)
    toast.error('Bad User Credentials')
  }
}
SignUp.jsx
const onSubmit = async (e) => {
  e.preventDefault()
 
  try {
    const auth = getAuth()
 
    // Since the function doesn't take name
    // we use the update profile function, which is used to update the name and photo of the user
    const userCredential = await createUserWithEmailAndPassword(
      auth,
      email,
      password
    )
 
    // we'll use this user info to add to the database
    const user = userCredential.user
 
    updateProfile(auth.currentUser, {
      displayName: name,
    })
 
    const formDataCopy = { ...formData }
    delete formDataCopy.password
    formDataCopy.timestamp = serverTimestamp()
 
    await setDoc(doc(db, 'users', user.uid), formDataCopy)
 
    navigate('/')
  } catch (error) {
    // console.log(error)
    toast.error('Unable to Register')
  }
}

Sign In with Google

const onGoogleClick = async () => {
  try {
    const auth = getAuth()
    const provider = new GoogleAuthProvider()
    // Opens the pop-up and returns the signed in User
    const result = await signInWithPopup(auth, provider)
    const user = result.user
 
    const userRef = doc(db, 'users', user.uid)
    const userSnap = await getDoc(userRef)
 
    // if the user does not exist in the users collection create one
    if (!userSnap.exists()) {
      await setDoc(doc(db, 'users', user.uid), {
        name: user.displayName,
        email: user.email,
        timestamp: serverTimestamp(),
      })
    }
    navigate('/')
  } catch (error) {
    toast.error('Could not login with google')
  }
}

Pagination

To set up pagination, fetch say 10 items on the page load (useEffect). Keep track of the last fetched item

const [lastFetchedListing, setLastFetchedListing] = useState(null)
// ...
 
// Inside useEffect
const listingsRef = collection(db, 'listings')
// Create a query
const q = query(
  listingsRef,
  where('type', '==', params.categoryName),
  orderBy('timestamp', 'desc'),
  limit(10)
)
// Execute the query
const querySnap = await getDocs(q)
 
// Set the last fetched Item
const lastVisibleListing = querySnap.docs[querySnap.docs.length - 1]
setLastFetchedListing(lastVisibleListing)
 
const listings = []
 
querySnap.forEach((doc) => {
  listings.push({
    id: doc.id,
    data: doc.data(),
  })
})
 
setListings(listings)

Now on LoadMoreClick make a query using the startAfter function.

const listingsRef = collection(db, 'listings')
 
const q = query(
  listingsRef,
  where('type', '==', params.categoryName),
  orderBy('timestamp', 'desc'),
  startAfter(lastFetchedListing), // query with startAfter
  limit(10)
)
 
const querySnap = await getDocs(q)
 
// Update the last fetched Item
const lastVisibleListing = querySnap.docs[querySnap.docs.length - 1]
setLastFetchedListing(lastVisibleListing)
 
const listings = []
 
querySnap.forEach((doc) => {
  listings.push({
    id: doc.id,
    data: doc.data(),
  })
})
 
// Make sure to add the new Items, with the previous listings and not remove the previous once by setting the state just to the new 10 fetched items
setListings((prevListings) => [...prevListings, ...listings])