Read Data from Firestore and React custom hooks for realtime data.

Prequisites

  • React Custom Hooks
  • Firebase Initialization

Contents

  • Firestore Read Methods

    • reference and doc()
    • getDoc() and snapShot
    • collection(), query() and getDocs()
    • onSnapshot and realtime data subscription
  • React custom hooks for realtime data subscription

    • useDocument()
    • useCollection()
    • useSpecificCollection()

Custom Hooks in React

Official Documentaion

Firestore Read Methods

Official Documentaion

Firestore is a NoSQL database service offered by google's Firebase.

Things You Need to know about firestore methods

  • reference and doc()
  • getDoc() and snapShot
  • collection(), query() and getDocs()
  • onSnapshot()

Reference and doc()

A reference points to a place in Firestore database.

doc() is a function provided by firestore to get a reference in the Database.

The Reference itself does not contain any data, It just points to a place in Database where data may or may not exists.

doc() takes in three arguments

  • db instance initialised by getFirestore(app)
  • key of a collection
  • key of a document
const messageReference = doc(db, "messages", "messageId")

now we have reference which points to a particular message reference where document may or may not exists

getDoc() and snapShots

Now that we have a reference, we can get the document by passing the reference as an argument to the getDoc() function. Remeber getDoc() returns a promise.

const messageSnapShot = await getDoc(messageReference);

now snapShots has two methods which you need to know.

  • exists() - check if document exists -returns boolean
  • data() - get data of the document -returns data

now you have learnt to get a single document from firestore.

collection(), query() and getDocs()

Now what if you want multiple documents from a collection ?

collection() is similar to doc() but takes only two arguments -

  • db instance
  • key or id of a collection in database
const messagesCollectionReference = collection(db, "messages")

it also returns a reference to an entire collection.

query() is function to get reference(query) to documents in a collection which matches a specific condition specified by where() function.

It takes two arguments

  • collectionReference which we got above
  • where() function which itself takes 3 arguments
    • A "key" in the document example "author"
    • an "operator" as a string example "=="
    • A value to equate to example "LoggedInUserId"

putting it all together -

const messagesLoggedInUserQuery = query(messagesCollectionReference, where("author", "==", LoggedInUserId))

now getDocs() is similar to getDoc() , but getDocs will return an iterator of all the snapshots matching the query.

const messagesFromUserSnapshots = await getDocs(messagesLoggedInUserQuery)

(()=>messagesFromUserSnapshots.forEach(snapshot=>console.log(snapshot.data()))
()

You can also push all the snapshot.data() to an array defined before.

To get All Documents from a collection without any condition, just omit the where() as the second argument in query().

onSnapshot()

onSnapshot() is a listener provided from firestore. it takes two arguments

  • singleDocumentReference or query
  • A callback function which runs everytime any data pointed by the first argument changes

The CallBack also runs once on initialition it is passed an argument by the listener(), either a single snapshot or an array of snapshots based on first argument

The onSnapshot() itselfs returns a cleanup(Unsubscriber) function which needs to be called to end the data subscription

const unSubscribe = onSnapshot(messageReference, callback);
///do something

unSubscribe()

Now we know how to get single and multiple documents from firestore and also subscribe to realtime data. Now we can procced to building hooks.

React Custom Hooks for Realtime Subscription

Now that we know all the required firestore methods, we have to tackle react specific methods to initialize realtime subscription

The Requirements are

  • We have to call the onSnapshot Listener on Component First Mount
  • We have to extract and store data in a state variable
  • onSnapshot init-> on db change -> Callback -> updateState -> updateView
  • Then we have to destroy the listener on component unmount

Lets tackle the above requirements step by step

  • To Initialize onSnapshot only once on component mount we have to use useEffect with empty dependency array
  • To store the Data we can use useState hook
  • To Destroy the listener we can return unSubscribe method from useEffect, which will be used for cleanUp

Single Document Hook

Single DocumentHook can be a re-usable hook , which takes, collectionKey and documentKey as Parameter

// Initialize App and Database before
const useDocument = (collectionKey, documentKey) => {
    const [data, setData] = useState(null);
    useEffect(() => {
        const documentReference = doc(db, collectionKey, documentKey);

        const unSubcribe = onSnapshot(documentReference, (documentSnapshot) => {
            const newData = documentSnapshot.data();
            setData(newData);
        })

        return unSubcribe // cleanup Function when component unMounts
    }, [])
    return data; // return State Variable which can be consumed in the component
}

Full Collection Hook

Full CollectionHook can also be a re-usable hook , to listen to entire collection by usong empty query, which takes, documentKey as Parameter

// Initialize App and Database before
const useDocument = (documentKey) => {
    const [data, setData] = useState(null);
    useEffect(() => {
        const q = query(collection(db, documentKey));
        let newData = [];
        const unSubcribe = onSnapshot(q, (querySnapshot) => {
            querySnapshot.forEach(snapshot=>newData.push(snapshot.data()));
            setData(newData);
        })

        return unSubcribe // cleanup Function when component unMounts
    }, [])
    return data; // return State Variable which can be consumed in the component
}

Specific Query Hook

Specific Query Hook can be set up once with a custom query , it does not take any parameters, as query will be already defined ex- listen to messages of logged in user

// Initialize App and Database before
const useDocument = () => {
    const [data, setData] = useState(null);
    useEffect(() => {

        const q = query(messagesCollectionReference, where("author", "==", LoggedInUserId);
        let newData = [];
        const unSubcribe = onSnapshot(q, (querySnapshot) => {
            querySnapshot.forEach(snapshot=>newData.push(snapshot.data()));
            setData(newData);
        })

        return unSubcribe // cleanup Function when component unMounts
    }, [])
    return data; // return State Variable which can be consumed in the component
}

Thats it Folks, Note - At the time of writing, firebase version referenced is 9.8.0