Firebase as a React Hook
Tags: firebase, react, javascript
Published: 2020-01-15 18:20:22

---
title: Firebase as a React Hook
published: true
date: 2020-01-15 12:12:51 UTC
tags: firebase, react, javascript
canonical_url: https://pragli.com/blog/firebase-as-a-react-hook/
---

 ![Firebase as a React Hook](https://pragli.com/blog/content/images/2020/01/cullan-smith-BdTtvBRhOng-unsplash.jpg)

In a prior post, ["How we use Firebase instead of React with Redux,"](https://pragli.com/blog/how-we-use-firebase-instead-of-redux-with-react/) I discussed how we created a `withDbData` function to load data from Firebase Realtime Database (RTDB) into React conveniently.

Now that we've switched to writing most of our components as functions, I wanted a hook equivalent for loading state. In this post, I'll explain how to use and how I implemented useDbDatum / useDbData, two hooks for generically loading data from Firebase RTDB.

**Note: you can get the code as a gist [here](https://gist.github.com/dsafreno/8f654b10836b98cd5592785a0c8cb705).**

## Usage

`useDbDatum` is a hook that loads a single datum at a single path in Firebase RTDB.

You could, for instance, use `useDbDatum` as follows:

<!--kg-card-begin: code-->
```javascript
const Name = ({uid}) => {
  let name = useDbDatum(`users/${uid}/name`)
  return <div>{name}</div>
}
```
<!--kg-card-end: code-->

Note that `name` is `null` initially, but the component rerenders with the value once it loads.

`useDbData` loads multiple paths at the same time, returning an object where the keys are the paths and the values are the data in Firebase RTDB.

Most of the time, you'll want to use `useDbDatum` over `useDbData` - it's more convenient and direct - but I've had to switch over once or twice in our code base.

An example for `useDbData`:

<!--kg-card-begin: code-->
```javascript
const SortedStudentNames = ({classUid}) => {
  let students = useDbDatum(`classes/${classUid}/students`);
  let uids = Object.keys(students || {});
  let paths = studentIds.map(id => `students/${id}/name`);
  let nameValues = useDbData(paths);
  let names = Object.values(nameValues || {});
  names.sort();
  return <p>{names.join(', ')}</p>
}
```
<!--kg-card-end: code-->
## Implementation

During this implementation, I learned a lot about React hooks. I found it pretty quick to get up and running with `useReducer` and `useEffect`, but the tricky key to getting `useDbData` working was `useRef`.

`useRef` provides an escape hatch from the other state of functional React components, which generally trigger rerenders when updated. If you're ever yearning to replace using `this.something = {}` in a React class component, `useRef` may be your solution.

Doesn't that `useRef` seem hacky? I thought so too, but I discovered that I wasn't the only one who used `useRef` this way. Dan Abramov, one of the most famous contributors to React and author of Redux / create-react-app, also uses `useRef` this way. Check out his blog post ["Making setInterval Declarative with React Hooks"](https://overreacted.io/making-setinterval-declarative-with-react-hooks/) for more.

**Note: you can get the code as a gist [here](https://gist.github.com/dsafreno/8f654b10836b98cd5592785a0c8cb705).**

<!--kg-card-begin: code-->
```javascript
import React, { useReducer, useEffect, useRef } from 'react';
import firebase from 'firebase/app';
import equal from 'deep-equal';

function filterKeys(raw, allowed) {
  if (!raw) {
    return raw;
  }
  let s = new Set(allowed);
  return Object.keys(raw)
    .filter(key => s.has(key))
    .reduce((obj, key) => {
      obj[key] = raw[key];
      return obj;
    }, {});
}

export const useDbData = (paths) => {
  let unsubscribes = useRef({})
  let [data, dispatch] = useReducer((d, action) => {
    let {type, path, payload} = action
    switch (type) {
      case 'upsert':
        if (payload) {
          return Object.assign({}, d, {[path]: payload})
        } else {
          let newData = Object.assign({}, d)
          delete newData[path]
          return newData
        }
      default:
        throw new Error('bad type to reducer', type)
    }
  }, {})
  useEffect(() => {
    for (let path of Object.keys(paths)) {
      if (unsubscribes.current.hasOwnProperty(path)) {
        continue
      }
      let ref = firebase.database().ref(path)
      let lastVal = undefined
      let f = ref.on('value', snap => {
        let val = snap.val()
        val = paths[path] ? filterKeys(val, paths[path]) : val
        if (!equal(val, lastVal)) {
          dispatch({type: 'upsert', payload: val, path})
          lastVal = val
        }
      })
      unsubscribes.current[path] = () => ref.off('value', f)
    }
    let pathSet = new Set(Object.keys(paths))
    for (let path of Object.keys(unsubscribes.current)) {
      if (!pathSet.has(path)) {
        unsubscribes.current[path]()
        delete unsubscribes.current[path]
        dispatch({type: 'upsert', path})
      }
    }
  })
  useEffect(() => {
    return () => {
      for (let unsubscribe of Object.values(unsubscribes.current)) {
        unsubscribe()
      }
    }
  }, [])
  return data
}

export const useDbDatum = (path, allowed=null) => {
  let datum = useDbData(path ? {[path]: allowed} : {})
  if (datum[path]) {
    return datum[path]
  }
  return null
}
 
```
<!--kg-card-end: code-->
## Conclusion

Have any thoughts or questions about `useDbData/Datum`? Let me know at doug@pragli.com or on Twitter [@dougsafreno](https://twitter.com/dougsafreno)

## Learn More about Pragli

I'm the co-founder of Pragli, a virtual office for remote teams. Teams use Pragli to communicate faster and build closeness with one another. Learn more [here](https://pragli.com/blog/firebase-as-a-react-hook/pragli.com).