Listening for Real Time Changes in Firestore Database (Using onSnapshot Method)
Working on: Tweeter App (Twitter Clone)
onSnapshot
- Is one of the methods that can be used on the CollectionReference Object alongside other methods such as add, get and more.
- Attaches a listener and listens for any operational changes on our Firestore database (in real time) — such as CRUD (Create, Read, Update and Delete) operations.
First of all, to be able to use onSnapshot we need to access the CollectionReference object through:
firebase.firestore().collection()
collection() gets a CollectionReference instance that refers to the collection at the specified path. In our case, it will be the collection called “tweets” that is stored in the Cloud Firestore.
And by using the method, .onSnapshot (which takes a callback so we can iterate through each snapshot), it will return an array containing the documents in the collection referred to (but not necessarily the data we need — yet).
useEffect(async () => {setUsername(userObj.displayName);firebaseDB.collection("tweets").onSnapshot((snapshot) => {console.log(snapshot.docs);setTweets(tweetArray);});}, []);
And if we check the console, this is what is returned. So, it’s correct. I do have two documents inside my collection. But to be able to access the contents we want to access, we have to do a bit more.
Next, we have to iterate over snapshot.docs by mapping over it:
snapshot.docs.map((document) => {console.log(document.data())
}
Note that to access the fields on each document, we have to call the data() method on each document in the collection. data() retrieves all fields in the document as an object. That is why we are mapping over the array returned by onSnapshot().
And if we check our console, this is what we get. We are successfully retrieving all the documents from the collection.
In my case, I want to be able to access the fields from each document (tweet) and display it. So I used one of React’s hooks: useState() to be able to easily access and set the state.
const [tweets, setTweets] = useState([]);useEffect(async () => {firebaseDB.collection("tweets").onSnapshot((snapshot) => {const tweetArray = snapshot.docs.map((document) => {return { id: document.id, ...document.data() };});setTweets(tweetArray);});}, []);return(<div className="tweets-container">{tweets.map((tweet) => (<divclassName={checkUser(tweet.userId) ? "my-tweet" : "tweet"}key={tweet.id}>{checkUser(tweet.userId) && (<div className="edit-buttons"><i class="fas fa-trash-alt"></i><i class="fas fa-edit"></i></div>)}<h2>{tweet.text}</h2></div>))}</div>)
Note: i’ve only included snippets of my code.
As you can see on what I returned, I am mapping over the tweets state and accessing each individual value and displaying it.
Now, once I send out a tweet, it is displaying it in real time — there is no need to reload the page.
Demo:
- Sending a tweet
2. Without reloading the page or waiting, it automatically pops up:
And this is all because of onSnapshot!
This is what the collection and its documents and fields looks like on Cloud Firestore: