How we use Firebase instead of Redux (with React)
Tags: development, javascript, react, webdev
Published: 2020-01-08 02:29:21

---
title: How we use Firebase instead of Redux (with React)
published: true
date: 2019-09-24 16:05:01 UTC
tags: Development,JavaScript,React,WebDev
canonical_url: https://pragli.com/blog/how-we-use-firebase-instead-of-redux-with-react/
---

 ![How we use Firebase instead of Redux (with React)](https://pragli.com/content/images/2019/09/fb-react-redux.png)

This article explains how Pragli uses Firebase Realtime Database like a Redux store for our React front-end.

## Background

Vivek and I use Firebase with React to operate Pragli.

For those who aren't familiar, [Firebase Realtime Database](https://firebase.google.com/products/realtime-database/) (RTDB) provides in-browser (or in-app) data reading, writing, and subscribing. One client can simply write to a JSON document, and the document immediately propagates to all other clients. This largely eliminates the need for server code.

Data is represented as one large JSON document with subdata referenced by "routes." For instance, my user in the JSON document below is at the route `users/dsafreno`.

<!--kg-card-begin: code-->
```javascript
{
  "teams": {
    "Pragli": { ... },
    ...
  },
  "users": {
    "dsafreno": { ... },
    "vnair611": { ... },
    ...
  }
}
```
<!--kg-card-end: code-->

For a production application, the client can't do everything, largely for security reasons. For instance, sending emails or authenticating with integrations requires tokens that should not be shared with the client. We fill in the gaps using Firebase's [Cloud Functions](https://firebase.google.com/products/functions/).

## Wiring Firebase RTDB and React Sucks (By Default)

The problem with Firebase RTDB is that it isn't designed for React, so wiring the two together sucks. We ended up doing the same thing over and over again:

- subscribe to a bunch of data in `componentDidMount` 
- unsubscribe in `componentWillUnmount`
- perform our "data mounted" logic in `componentDidUpdate` 
```javascript
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = { user: null, team: null };
  }
  componentDidMount() {
    let {userId, teamId} = this.props;
    // subscribe to user data
    let userRef = firebase.database().ref(`users/${userId}`);
    let userOff = userRef.on('value', (snap) => {
      this.setState({user: snap.val()});
    }
    this.userOff = () => ref.off('value', userOff);
    // subscribe to team data
    let teamRef = firebase.database().ref(`teams/${teamId}`);
    let teamOff = teamRef.on('value', (snap) => {
      this.setState({team: snap.val()});
    }
    this.teamOff = () => ref.off('value', teamOff);
  }
  componentDidUpdate(prevProps, prevState) {
    if (!prevState.user && this.state.user) {
        // first time we got user data!
    }
    if (!prevState.team && this.state.team) {
        // first time we got team data!
    }
  }
  componentWillUnmount() {
    this.userOff();
    this.teamOff();
  }
  render() {
    let { user, team } = this.state;
    if (!user || !team) {
      return null;
    }
    // ...
  }
}

export default Example
```
<!--kg-card-end: code-->

Ugly, right? That's a ton of boilerplate for a React component to subscribe to the data at two routes in Firebase. Components that required more data were even worse.

So we brainstormed how we could do better, considering a few solutions.

## Ideas

**Pass more data as props from higher-level components**

We considered subscribing to data in a high level component and passing it down to child components.We started implementing this in some places, but we ultimately got frustrated because it caused too many child / intermediary component re-renders, slowing down the application.

**Load data from Firebase RTDB → Redux → React**

[Redux](https://redux.js.org/) is a state container for JS apps commonly used alongside React.

We considered syncing our data into Redux from Firebase RTDB and then subscribing to the Redux store for data. There's even a [library](https://github.com/prescottprue/react-redux-firebase) for making React, Redux, and Firebase RTDB play nicely together.

But isn't the whole point of Firebase RTDB to have one easy-to-use source of state? Why duplicate with Redux?

We decided we wanted to come up with a solution that didn't involve piping state through Redux.

Which led us to our final solution...

## Autoload Data with Specs

Ultimately, we decided to write our own wrapper function to make accessing Firebase RTDB more convenient.

The key idea is to statically specify which data your component needs via a static template. Once the data becomes available, Firebase RTDB fetches that data and passes it directly into the component as props.

We use the following schema:

<!--kg-card-begin: code-->
```javascript
const MY_DATA_SPEC = {
  name: 'myData',
  template: 'data/{myUid}',
  await: true
};
```
<!--kg-card-end: code-->

This schema specifies that the data at route `data/{myUid}` is passed into the component as the `myData` prop (`myUid` is assumed to be passed in as a prop from the parent).

The `await: true` prevents the component from mounting until it has received some data at that path (so that `componentDidMount` always has data).

## Wiring it together - withDbData

We wrote `withDbData` to conveniently load components with the data in this spec.

Here's what the above component looks like now:

<!--kg-card-begin: code-->
```javascript
class Example extends React.Component {
  componentDidMount() {
    // first time we got data!
  }
  render() {
    let {user, team} = this.props;
    // don't need to null check since we await the data!
  }
}

const USER_SPEC = {
  name: 'user',
  template: 'users/{userId}',
  await: true
};

const TEAM_SPEC = {
  name: 'team',
  template: 'teams/{teamId}',
  await: true
};

export default withDbData([USER_SPEC, TEAM_SPEC])(Example)
```
<!--kg-card-end: code-->

Here's the source code (MIT license, feel free to use it). It's also available on Github [here](https://gist.github.com/dsafreno/43850a4333b4a56a57f4e64d730ccc8b).

<!--kg-card-begin: code-->
```javascript
import React from 'react';
import firebase from 'firebase/app';
import equal from 'deep-equal';

export function withDbData(specs) {
  let propToSpecs = {};
  for (let spec of specs) {
    let {propIds} = parseSpec(spec);
    for (let propId of propIds) {
      if (!propToSpecs[propId]) {
        propToSpecs[propId] = [];
      }
      propToSpecs[propId].push(spec);
    }
  }

  return (Child) => {
    let Wrapper = class extends React.PureComponent {
      constructor(props) {
        super(props);
        this.unmounting = false;
        this.offs = {};
        this.state = {};
      }
      subscribeToSpec(spec) {
        let { name, keys } = spec;
        let { propIds, formatPath } = parseSpec(spec);
        let path = formatPath(this.props);
        if (!path) {
          return;
        }
        let ref = firebase.database().ref(path);
        let offFunc = ref.on('value', (snap) => {
          let dat = keys ? filterKeys(snap.val(), keys) : snap.val();
          if (equal(dat, this.state[name])) {
            return;
          }
          this.setState({
            [name]: dat,
          });
        });
        let hasBeenOffed = false;
        let off = () => {
          if (hasBeenOffed) {
            return;
          }
          hasBeenOffed = true;
          if (!this.unmounting) {
            this.setState({
              [name]: null,
            });
          }
          ref.off('value', offFunc);
        };
        for (let propId of propIds) {
          if (!this.offs[propId]) {
            this.offs[propId] = [];
          }
          this.offs[propId].push(off)
        }
      }
      componentDidMount() {
        for (let spec of specs) {
          this.subscribeToSpec(spec)
        }
      }
      componentDidUpdate(prevProps) {
        let resubs = new Set();
        for (let prop of Object.keys(propToSpecs)) {
          if (prevProps[prop] !== this.props[prop]) {
            if (this.offs[prop]) {
              for (let off of this.offs[prop]) {
                off();
              }
            }
            this.offs[prop] = [];
            for (let spec of propToSpecs[prop]) {
              if (resubs.has(spec.name)) {
                continue;
              }
              resubs.add(spec.name);
              this.subscribeToSpec(spec);
            }
          }
        }
      }
      componentWillUnmount() {
        this.unmounting = true;
        for (let offList of Object.values(this.offs)) {
          for (let off of offList) {
            off();
          }
        }
        this.offs = {};
      }
      render() {
        for (let spec of specs) {
          if (spec.await && !this.state[spec.name]) {
            return null;
          }
        }
        let childProps = Object.assign({}, this.props, this.state);
        return (<Child {... childProps} />);
      }
    }
    return Wrapper;
  }
}
```
<!--kg-card-end: code-->
## Conclusion

Did this help you learn how to better use Firebase with React? Do you have any follow up questions? Shoot me an email at doug@pragli.com, or follow up with me on Twitter [@dougsafreno](https://twitter.com/dougsafreno).