React Hooks with Firebase Firestore
Tags: firebase, react, webdev
Published: 2020-03-30 15:28:29

---
title: React Hooks with Firebase Firestore
published: true
date: 2020-03-11 13:00:07 UTC
tags: firebase,react,webdev
canonical_url: https://blog.logrocket.com/react-hooks-with-firebase-firestore/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/c4qxh8g899ixgn3ohp85.jpeg
---
**Written by [Brian De Sousa](https://blog.logrocket.com/author/briandesousa/)**✏️

What do React Hooks and Firebase have in common? They both accelerate development and reduce the amount of code you need to write to build something that would otherwise be complex.

It is actually quite incredible how fast you can put together a web app with data persistence when you couple the power and simplicity of Firestore with simple, efficient React function components and Hooks.

## How do Hooks accelerate development?

First, a quick refresher on React Hooks. Hooks allow you to define stateful logic as reusable functions that can be used throughout your React application. Hooks also enable function components to tie into the component lifecycle, previously only possible with class components.

When it comes to creating components that need to handle lifecycle events, React does not prescribe whether you should use function components and Hooks or more traditional class components.

That being said, function components and Hooks have quickly become a big hit in the React developer community — and with good reason. Function components and Hooks greatly reduce the amount code and verbosity of a React app compared to class components.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## How does Firestore accelerate development?

Firebase is a collection of services and tools that developers can piece together to quickly create web and mobile applications with advanced capabilities. Firebase services run on top of the Google Cloud Platform, which translates to a high level of reliability and scalability.

Firestore is one of the services included in Firebase. Firestore is a cloud-based, scalable, NoSQL document database. One of its most notable features is its ability to easily stream changes to your data to your web and mobile apps in real time. You will see this in action shortly in a sample app.

Web app development is further accelerated by the Firestore authentication and security rules model. The Firestore web API allows your web app to interact with your Firestore database directly from the browser without requiring server-side configuration or code. It’s literally as simple as setting up a Firebase project, integrating the API into client-side JavaScript code, and then reading and writing data.

React function components, Hooks, and the Firestore web API complement each other incredibly well. It’s time for to see all of these in action. Let’s take a look at an example grocery list web app and some of its code.

## The grocery list web app

To explore using React Hooks with Firebase, we need some sample code. Let’s use the grocery list web app as an example.

![Our Grocery List Web App](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/03/grocery-list-web-app.png?resize=730%2C386&ssl=1)<figcaption id="caption-attachment-15434">Grocery list web app.</figcaption>

You can [try the grocery list web app](https://fir-with-react-hooks.firebaseapp.com/) for yourself. Please ignore the CSS styles resurrected from a 1990s website graveyard — UI design is clearly not my strong suit.

If you haven’t tried the app out yet, you might be wondering how it works. It allows you to create a new grocery list. The grocery list’s URL can be shared with other users, who can then join the list and add their own grocery items to the list.

Grocery list items immediately appear on the screen as they are added to the database. This creates a shared experience, where multiple users can add items to the list at the same time and see each other’s additions.

The grocery list web app is built completely using React function components and Hooks. Grocery list and user data is persisted to Firestore. The web app itself is hosted using Firebase hosting.

Full source code for the grocery list app is available on GitHub in the [briandesousa/firebase-with-react-hooks](https://github.com/briandesousa/firebase-with-react-hooks/tree/logrocket-blog) repository.

## Firebase web app configuration and initialization

All calls to the Firebase web API to retrieve or update data on Firestore have been grouped together in `src/services/firestore.js`. At the top of this file, you will see Firebase app initialization code that looks like this:

```jsx
const firebaseConfig = {
    apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
    authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
    projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID
};
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
```

_[View the full source code here.](https://github.com/briandesousa/firebase-with-react-hooks/blob/logrocket-blog/src/services/firestore.js)_

In order to use Firebase services, you must provide some configuration to the `firebase.initializeApp` function. The configuration you are required to provide depends on which Firebase services you are using.

In this case, I am only using Firestore, so an API key, authentication domain, and project ID are all that is required. Once you have created a Firebase project and added a web app, your unique configuration settings can be found on the **General** tab of the project’s settings screen on the [Firebase console](https://console.firebase.google.com/).

At first glance, the Firebase configuration settings seem as though they should be private and not exposed in the browser. That is not the case, though: they are safe to include in your client-side JavaScript. Your application is secured using Firebase authentication and Firestore security rules. I won’t get into those details here, but you can read more about it [here](https://firebase.google.com/docs/firestore/security/get-started).

You may have also noticed that I replaced configuration values with React environment variables defined on the global `process.env` object. You probably don’t want to include this configuration in your source code repository, especially if your repository is publicly available and intended to be shared and cloned by other developers.

Developers are bound to download your code and run it without realizing they are consuming your Firebase resources. Instead, I have opted to include a sample [.env](https://github.com/briandesousa/firebase-with-react-hooks/blob/master/.env) file that documents the configuration settings that must be provided before running the app. When I am running the app myself locally, I have my own `.env.local` file that doesn’t get checked into source control.

## Writing data to Firestore

Once your Firebase configuration has been set up, getting started with writing to and reading data from your Firestore database requires very little code.

In its basic form, a Firestore database consists of collections of documents. A document can contain multiple fields of varying types, including a sub-collection type that allows you to nest document collections. All of this structure is generated on the fly as your JavaScript code makes calls to the Firebase API to write data.

For example, the following code creates a new grocery list document in the `groceryLists` collection:

```jsx
export const createGroceryList = (userName) => {
    return db.collection('groceryLists')
        .add({
            created: firebase.firestore.FieldValue.serverTimestamp(),
            users: [{ name: userName}]
        });
};
```

_[View the full source code here.](https://github.com/briandesousa/firebase-with-react-hooks/blob/logrocket-blog/src/services/firestore.js)_

Initially, when a grocery list document is created, I only store the name of the user creating the list and a timestamp for when the list was created. When the user adds their first item to the list, an `items` sub-collection is created in the document to hold items on the grocery list.

The Firebase console’s database screen does a great job visualizing how your collections and documents are structured in Firestore:

![Items Sub-collection Of A Document In Firestore](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/03/items-sub-collection.png?resize=730%2C342&ssl=1)<figcaption id="caption-attachment-15436">`items` sub-collection of a document in the `groceryLists` collection in Firestore.</figcaption>

Next, let’s look at how grocery list data is stored in React component state.

## Managing grocery list state

React components can have state. Prior to hooks, if you wanted to use the React state API, your React components had to be class components. Now you can create a function component that uses the built-in `useState` Hook. In the grocery list web app, you’ll find an example of this in the `App` component.

```jsx
function App() {
  const [user, setUser] = useState()
  const [groceryList, setGroceryList] = useState();
```

_[View the full source code here.](https://github.com/briandesousa/firebase-with-react-hooks/blob/logrocket-blog/src/App.js)_

The `App` component is the top-level component in the React component hierarchy of the grocery list web app. It holds onto the current user and grocery list in its state and shares that parts of that state with child components as necessary.

The `useState` Hook is fairly straightforward to understand and use. It accepts an optional parameter that defines the initial state to be used when an instance of the component is mounted (or, in other words, initialized).

It returns a pair of values, for which I have used [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) to create two local variables. For example, `user` lets the component access the current user state, which happens to be a string containing the user’s name. Then the `setUser` variable is a function that is used to update the user state with a new user name.

OK, great — the `useState` Hook lets us add state to our function components. Let’s go a little a deeper and look at how we can load an existing grocery list object from Firestore into the `App` component’s state as a side effect.

## Loading state from Firestore as a side effect

When a link to a grocery list is shared with another user, that link’s URL identifies the grocery list using the `listId` query parameter. We will take a look at how we access that query parameter later, but first we want to see how to use it to load an existing grocery list from Firestore when the `App` component mounts.

Fetching data from the backend is a good example of a component side effect. This is where the built-in `useEffect` Hook comes into play. The `useEffect` Hook tells React to perform some action or “side effect” after a component has been rendered in the browser.

I want the `App` component to load first, fetch grocery list data from Firestore, and only display that data once it is available. This way, the user quickly sees something in the browser even if the Firestore call happens to be slow. This approach goes a long way toward improving the user’s perception of how fast the app loads in the browser.

Here is what the `useEffect` Hook looks like in the `App` component:

```jsx
  useEffect(() => {
    if (groceryListId) {
      FirestoreService.getGroceryList(groceryListId)
        .then(groceryList => {
          if (groceryList.exists) {
            setError(null);
            setGroceryList(groceryList.data());
          } else {
            setError('grocery-list-not-found');
            setGroceryListId();
          }
        })
        .catch(() => setError('grocery-list-get-fail'));
    }s
  }, [groceryListId, setGroceryListId]);
```

_[View the full source code here.](https://github.com/briandesousa/firebase-with-react-hooks/blob/logrocket-blog/src/App.js)_

The `useEffect` Hook accepts two parameters. The first is a function that accepts no parameters and defines what the side effect actually does. I am using the `getGroceryList` function from the `firestore.js` script to wrap the call to the Firebase API to retrieve the grocery list object from Firestore.

The Firebase API returns a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that resolves a [`DocumentSnapshot`](https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentSnapshot) object that may or may not contain the grocery list depending on whether the list was found. If the promise rejects, I store an error code in the component’s state, which ultimately results in a friendly error message displayed on the screen.

The second parameter is an array of dependencies. Any props or state variables that are used in the function from the first parameter need to be listed as dependencies.

The side effect we just looked at loads a single instance of a document from Firestore, but what if we want to stream all changes to a document as it changes?

## Streaming data in real time from Firestore as a side effect

React class components provide access to various lifecycle functions, like `componentDidMount` and `componentWillUnmount`. These functions are necessary if you want to do something like subscribe to a data stream returned from the Firestore web API after the component is mounted and unsubscribe (clean up) just before the component is unmounted.

This same functionality is possible in React function components with the `useEffect` Hook, which can optionally return a cleanup function that mimics `componentWillUnmount`. Let’s look at the side effect in the `Itemlist` component as an example:

```jsx
    useEffect(() => {
        const unsubscribe = FirestoreService.streamGroceryListItems(groceryListId, {
            next: querySnapshot => {
                const updatedGroceryItems = 
                    querySnapshot.docs.map(docSnapshot => docSnapshot.data());
                setGroceryItems(updatedGroceryItems);
            },
            error: () => setError('grocery-list-item-get-fail')
        });
        return unsubscribe;
    }, [groceryListId, setGroceryItems]);
```

_[View the full source code here.](https://github.com/briandesousa/firebase-with-react-hooks/blob/logrocket-blog/src/scenes/EditList/ItemList/ItemList.js)_

The `streamGrocerylistItems` function is used to stream changes to the `items` sub-collection of a grocery list document as the data changes on Firestore. It takes an observer object and returns an `unsubscribe` function.

The observer object contains a `next` function that is called by the Firebase web API every time the `items` sub-collection changes. The `unsubscribe` function can be returned as is from the effect to stop streaming data from Firestore just before the `ItemList` component is unmounted. For example, when the user clicks the link to create a new grocery list, I want to stop the stream before displaying the create grocery list scene.

Let’s take a closer look at the `streamGrocerylistItems` function:

```jsx
export const streamGroceryListItems = (groceryListId, observer) => {
    return db.collection('groceryLists')
        .doc(groceryListId)
        .collection('items')
        .orderBy('created')
        .onSnapshot(observer);
};
```

_[View the full source code here.](https://github.com/briandesousa/firebase-with-react-hooks/blob/logrocket-blog/src/services/firestore.js)_

The `db` variable is an instance of the `Firestore` type defined in the Firebase web API. The API lets you retrieve a single instance of a collection or document using the `get` function or stream updates to a collection or document using the `onSnapshot` function. The `onSnapshot` function receives the observer object and returns the unsubscribe function that we saw previously.

Next, let’s look at how we can create a custom Hook to encapsulate some shared state and logic.

## Wrapping query string handling logic into a custom Hook

We want the grocery list app to use the list ID query parameter and react to changes to it. This is a great opportunity for a custom Hook that encapsulates the grocery list ID state and keeps it in sync with the value of the query parameter.

Here is the custom Hook:

```jsx
function useQueryString(key) {
    const [ paramValue, setParamValue ] = useState(getQueryParamValue(key));

    const onSetValue = useCallback(
        newValue => {
            setParamValue(newValue);
            updateQueryStringWithoutReload(newValue ? `${key}=${newValue}` : '');
        },
        [key, setParamValue]
    );

    function getQueryParamValue(key) {
        return new URLSearchParams(window.location.search).get(key);
    }

    function updateQueryStringWithoutReload(queryString) {
        const { protocol, host, pathname } = window.location;
        const newUrl = `${protocol}//${host}${pathname}?${queryString}`;
        window.history.pushState({ path: newUrl }, '', newUrl);
    }
    return [paramValue, onSetValue];
}
```

_[View the full source code here.](https://github.com/briandesousa/firebase-with-react-hooks/blob/logrocket-blog/src/hooks/useQueryString.js)_

I have designed `useQueryString` as a generic Hook that can be reused to link together any state with any query parameter and keep the two in sync. The Hook has two internal functions that are used to get and set the query string parameter.

The `getQueryParamValue` function accepts the parameter’s name and retrieves its value. The `updateQueryStringWithoutReload` uses the [browser history API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) to update the parameter’s value without causing the browser to reload. This is important because we want a seamless user experience without full page reloads when a new grocery list is created.

I use the `useState` Hook to store the grocery list ID in the Hook’s state. I return this state from the Hook in a way similar to how the built-in `useState` Hook works. However, instead of returning the standard `setParamValue` function, I return `onSetValue`, which acts as an interceptor that should only be called when the value of the state changes.

The `onSetValue` function itself is an instance of the built-in `useCallback` Hook. The `useCallback` Hook returns a memoized function that only gets called if one of its dependencies changes. Any props or state variables that are used by a `useCallback` hook must be included in the dependency array provided in the second parameter passed when creating the hook.

The end result is a custom Hook that initially sets its state based on a query parameter and updates that parameter when the state changes.

The `useQueryParameter` Hook is a highly reusable custom Hook. I can reuse it later on if I want to define a new type of state that I want to store in URL query string. The only caveat is that the state needs to be a primitive data type that can be converted to and from a string.

## Recap and where to explore next

We have explored a few of the built-in React Hooks, such as `useState`, `useEffect`, and `useCallback`, but there are still others that could help you as you build your application. The [React documentation](https://reactjs.org/docs/hooks-reference.html) covers all the built-in Hooks very clearly.

We have explored some of the Firebase web APIs that let you create, retrieve, and stream data from Firestore, but there are many other things you can do with the API. Try exploring the [Firestore SDK documentation](https://firebase.google.com/docs/reference/js/firebase.firestore) for yourself.

There are plenty of improvements that can be made to the grocery list web app, too. Try downloading the [source code from GitHub](https://github.com/briandesousa/firebase-with-react-hooks/tree/logrocket-blog) and running it yourself. Don’t forget that you will need to create your own Firebase project and populate the [.env](https://github.com/briandesousa/firebase-with-react-hooks/blob/master/.env) file first before running the app. Clone or fork the repo and have fun with it!

* * * 
## Full visibility into production React apps 

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, [try LogRocket.](https://www2.logrocket.com/react-performance-monitoring)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/eq752g8qhbffxt3hp9t4.png)

[LogRocket](https://www2.logrocket.com/react-performance-monitoring) is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free.](https://www2.logrocket.com/react-performance-monitoring)
* * *

The post [React Hooks with Firebase Firestore](https://blog.logrocket.com/react-hooks-with-firebase-firestore/) appeared first on [LogRocket Blog](https://blog.logrocket.com).