Terminology
Out of many things the firebase has to offer in this blogs, I will talk about two of it's usecases,
Authentication (Which comes out of box)
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' )
}
}
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])