November 8th 2018
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 😃 !
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.