A real-time hook with Firebase & React-Query

Aggelos Arvanitakis
3 min readMar 15, 2021

--

React Query logo

In an app that I’ve recently worked on, I had to interact with 2 different APIs: a typical JSON-based API from a django project, as well as a “real-time API” which was coming directly from Firebase. That meant that, when reading data, I had to sometimes read them from Firebase and sometimes from another JSON API.

I wanted used react-query for my remote data management needs, while also using the Firebase web client (as recommended) for all-things-firebase. While there’s nothing wrong with that, it felt a bit cumbersome to have two (2) different ways of querying and storing data. react-query came with batteries included on the state management side, but unfortunately, the same can’t be said about the Firebase client, which not only serves as a simple transport client, but also has a completely different API.

What I wanted is to have a unified way of querying & storing my data, without having to differentiate my approach based on where the data comes from. In this article we’ll implement just that.

Although fully optional, the first thing I did was to create a wrapper client for firebase so that I could add typings (I was working on a TS project), centralize the Firebase error handling logic and add some syntactic sugar to create a more barebones/clean API for my fetching needs. A stripped-down example of this can be seen below:

RealTimeAPI stripped down example

While this wrapper (nicknamed RealTimeAPI ) doesn’t wrap all Firebase methods, it’s complete enough to handle all basic read operations that we’ll need. I want to stress that this is something I chose to do, but under no means was it mandatory.

With this implemented, we can go ahead and explore our options with react-query. A thing to remember, is that react-queryis transport-agnostic; this means that it simply doesn’t care where and how data gets fetched, as long as a Promise gets resolved. With this in mind, we can go ahead and create a new useRealTimeQuery hook which will be an alternative to the useQuery hook that react-query already exposes. This would look something like the following:

A hook for subscribing to real-time data using react-query

Some things to unpack about the hook above:

  • It uses the firebase path key as the react-query’s query key since it’s guaranteed to be stable & unique
  • It creates an adapter interface between the callback-based land that firebase lives in and the Promise-based one that react-querylikes. You’d think that the data Promise endlessly hangs and never resolves, but react-queryresolves it instantly when queryClient.setQueryData is called.
  • It automatically subscribes to updates and re-renders the React component when new data arrives
  • It automatically unsubscribes if the React component gets unmounted thus not adding to your FB transport limits, nor unnecessarily stressing the main thread
  • Although isLoading is initially true while waiting for our first batch of data, it will never be true again since updates will be coming in real-time.

With this, we are pretty much done. We can do:

const { data, isLoading, error } = useRealTimeQuery('/me/friends');

and it would work exactly how you’d expect it to.

As a final note, if you want to transform the data that comes in from firebase, react-query supports data selectors which allow you to do just that.

Thanks for reading!

P.S. 👋 Hi, I’m Aggelos! If you liked this, consider following me on twitter or medium 😀

--

--