Unsubscribing from Firestore Realtime updates in React

firestore logo

I recently built Roll Call my attendance taking Android app utilizing Firestore as my database. When I went to build the companion web app with React and Redux, I was faced with the issue of how to unsubscribe from the realtime updates when I was finished with them. Every good developer guards against memory leaks 😃 !

The Problem

Unsubscribing is easy as you can see here from the official docs.

var unsubscribe = db.collection("cities")
    .onSnapshot(function () {});
// ...
// Stop listening to changes
unsubscribe();

The problem I was encountering was with the integration with React and Redux. My flow was as follows.

📁 component (componentDidMount, onChange) -> 
📁 action creators (subscribe to realtime updates) ->
📁 reducers

Getting the unsubscribe function to the component was easy enough. I just returned it from the action creator.

export const getAllSubGroups = (userId, topLevelDocId) => dispatch => {
  const unsubscribe = db
    .collection("sub_group")
    .where("userId", "==", userId)
    .where("topLevelDocId", "==", topLevelDocId)
    .onSnapshot(querySnapshot => {
      dispatch({
        type: GET_SUB_GROUPS,
        payload: querySnapshot.docs
      });
    });
  return unsubscribe;
};

Then I could access it in the calling method in the component. Here it is a select element with an onChange handler.

handleChange = e => {
  const { value } = e.target;
  const { user } = this.props;
  if (value !== "") {
    const unsubscribe = this.props.getAllSubGroups(user.uid, value);
  }
};

Now I have the unsubscribe in the handleChange method, but how to get it into componentWillUnmount? For this I removed the const and attached the unsubscribe to this making it available to the component.

handleChange = e => {
  const { value } = e.target;
  const { user } = this.props;
  if (value !== "") {
    this.unsubscribe = this.props.getAllSubGroups(user.uid, value);
  }
};

Now it can called in the componentWillUnmount lifecycle.

componentWillUnmount = () => {
  // First check that it exists
  this.unsubscribe && this.unsubscribe();
};

Since this method is a select onChange handler, the Firestore could be subscribed to multiple times with different queries. So now we can check for the existence of a previous unsubscribe and cancel it before subscribing to a new query.

handleChange = e => {
  const { value } = e.target;
  const { user } = this.props;
  if (value !== "") {
    this.unsubscribe && this.unsubscribe();
    this.unsubscribe = this.props.getAllSubGroups(user.uid, value);
  }
};

This approach has been working well for me. Let me know if you have found a better solution.


I'm Brandon Lehr, your friendly, neighborhood developer

✱ May or may not be friendly, most likely not your neighbor