React with Firebase: Firestore setup

tallangroberg

Tallan Groberg

Posted on January 13, 2020

React with Firebase: Firestore setup

who is this article for?

anyone trying to set up a serverless database option.

To be sure I don't waste your time and this is actually what you are looking for...

this is the github repo

this is the working product on surge

how to use it?

think of this as a small piece of functionality to incorporate into a much larger app.

what we will learn.

this is an example of the most basic firestore setup for a react application.

prerequisites:

create-react-app installed.

firebase account

understanding of react and javascript.

basic understanding of npm and firebase.

I will be using bash command line as well as vscode

the first thing we need to do is set up our firebase app inside the firebase console.

if you don't have a firebase console already you can make one by clicking here to get started

click add project.

Alt Text

name your project.

Alt Text

select your default account.

Alt Text

click where it says database.

Alt Text

click where it says to make a database.

Alt Text

start in test mode and once everything is working change back to production mode.

Alt Text

choose a location. Ideally where your users are likely to be and not necessarily where you are.

Alt Text

next you need to register your app.

Alt Text

name your app.

Alt Text

now we need to add firebase to our react app.

start by making a new react app from the command-line.

create-react-app firestore-tutorial
Enter fullscreen mode Exit fullscreen mode

cd into the app

cd firestore-tutorial
Enter fullscreen mode Exit fullscreen mode

once installed, make a new folder called firebase inside the src folder. you can make the file at the same time too!

mkdir src/firebase && touch src/firebase/index.js
Enter fullscreen mode Exit fullscreen mode

The above command will make a folder by walking down the folder structure and then, after the && sign, it will make a file in the folder you just created.

start your server by going back to the command line to running npm start

npm start
Enter fullscreen mode Exit fullscreen mode

now we go back to the firebase console and grab our firebase SDK and set that up inside of a file that we can use app wide.

hover on the cog next to project overview in the top left corner of the project page and click on project settings.

Alt Text

this takes you to a page providing general settings. scroll down to the your apps section and you will see an HTML snippet.

if this was just javascript then we would need to add this to the index.html of the webpage.

since this is a React project we have to add only the javascript inside the script tag and add that to the index.js inside the firebase folder

copy and paste your firebase SDK into your firebase/index.js (remember that this is different from you index.js below your App.test.js inside the src folder.)

add the exports and imports, it should look something like this.


import firebase from 'firebase'
import 'firebase/firestore'

firebase.initializeApp({
  apiKey: "super secret don't tell",
  authDomain: "firestore-tutoral-the-first.firebaseapp.com",
  databaseURL: "https://firestore-tutoral-the-first.firebaseio.com",
  projectId: "firestore-tutoral-the-first",
  storageBucket: "firestore-tutoral-the-first.appspot.com",
  messagingSenderId: "super secret don't tell",
  appId: "super secret don't tell",
  measurementId: "super secret don't tell"
});


let db = firebase.firestore()


export default {
  firebase, db
}
Enter fullscreen mode Exit fullscreen mode

now import firebase into the App.js at the top of the file like so.

import firebase from './firebase/index'
Enter fullscreen mode Exit fullscreen mode

then inside the App component add a console log

console.log(firebase)
Enter fullscreen mode Exit fullscreen mode

open the dev tools and you should see your config object.

Alt Text

now you have access to firebase at the top of your component tree which means that you have firebase anywhere in your app.

now make sure that you have access to your database by adding .db to the console.log

console.log(firebase.db)
Enter fullscreen mode Exit fullscreen mode

in the dev tools, you should see Firestore with all its helper methods.

let's use the infamous todo list to test our firestore capabilities.

I always like to break down my development into the smallest possible step forward. this means sending anything at all the firestore.

set up the function for firestore like so.

firebase.db.collection('todo').add({})
Enter fullscreen mode Exit fullscreen mode

we want to know what happened after we sent it, we want this function to let us know if it sent or failed. A prefect case for .then and .catch

change the function to look like this.


 firebase.db.collection('todo').add({title: 'first todo', description: 'new todo' })
    .then(documentReference => {
      console.log('document reference ID', documentReference.id)
    })
    .catch(error => {
      console.log(error.message)
    })

Enter fullscreen mode Exit fullscreen mode

this is almost exactly like the docs

now we want to add the button to send this to firebase. To do that, we need to make this function a variable so that we don't have to add this whole function inside an onClick for a button.

after you get rid of all the react boiler plate and add the sendTodo in front of the firebase.db function you should have a component that looks like this.

import React from 'react';
import firebase from './firebase/index'

const App = () => {

  console.log(firebase.db)

  const sendTodo = () => { firebase.db.collection('todo').add({title: 'first todo', description: 'new todo' })
    .then(documentReference => {
      console.log('document reference ID', documentReference.id)
    })
    .catch(error => {
      console.log(error.message)
    })

  }

  return (
    <div>
      <h1>send todo</h1>
        <button onClick={sendTodo}>click here to send</button>
    </div>
  );
};

export default App;

Enter fullscreen mode Exit fullscreen mode

go to localhost:3000 and click the button to send todo.

this will give you a document reference id.

Alt Text

if you go back to your firebase console and click on database.

You will notice that some changes have occurred.

Alt Text

congrats!!

you have set up a serverless react app.

we still have to get this displayed to the screen.

the best practice would be to save everything to state as soon as the component renders.

a perfect job for useState to store the todos and useEffect to save them as soon as the component renders.

add useState and useEffect at the top.

import React, {useEffect, useState} from 'react';
Enter fullscreen mode Exit fullscreen mode

make the piece of state at the top of the component.

  const [todos, setTodos] = useState([])

Enter fullscreen mode Exit fullscreen mode

the todo is an object, so we want todos to be an array of objects [{..}, {..} ect...]

now make the function to get the data from firestore and save it to the todos state with useEffect.

useEffect( () => {
        getTodos()
    }, [])

    const getTodos = () => {
      firebase.db.collection('todo').get()
        .then(querySnapshot => {
        querySnapshot.forEach( doc => {

          setTodos(prev => ([...prev, doc.data()]))
        })
      })
      .catch(err => {
        console.log(err.message)
      })
    }

Enter fullscreen mode Exit fullscreen mode

now we can display those todos to the browser.

add this line inside the render method and div

        {todos.length === 0 ? null : todos.map(todo => <h1 >{todo.title}</h1>) }
Enter fullscreen mode Exit fullscreen mode

here is what the App.js looks like so far...

import React, {useEffect, useState} from 'react';
import firebase from './firebase/index'

const App = () => {
  const [todos, setTodos] = useState([])

    useEffect( () => {
        getTodos()
    }, [])

    const getTodos = () => {
      firebase.db.collection('todo').get()
        .then(querySnapshot => {
        querySnapshot.forEach( doc => {
          setTodos(prev => ([...prev, doc.data()]))
        })
      })
      .catch(err => {
        console.log(err.message)
      })
    }


  const sendTodo = () => {
    firebase.db.collection('todo').add({title: 'first todo', description: 'new todo', })
    .then(documentReference => {
      console.log('document reference ID', documentReference.id )
    })
    .catch(error => {
      console.log(error.message)
    })
  }


  return (
    <div>
      <h1>send todo</h1>
        <button onClick={sendTodo}>click here to send</button>
         {todos.length === 0 ? null : todos.map(todo => <h1 >{todo.title}</h1>) }
    </div>
  );
};
export default App;

Enter fullscreen mode Exit fullscreen mode

now let's make it so that we send data based on user input.

let's make an initial state and have form inputs equal that, it will be an object.

const initstate = { title: '', description: ''}
  const [inputs, setInputs] = useState(initstate)
Enter fullscreen mode Exit fullscreen mode

add the form and input fields to change this state.

 <form onSubmit={sendTodo}>
          <input name='title'
            placeholder="title" 
            value={inputs.title}
            onChange={handleChange}/>
          <input 
            name='description'
            value={inputs.description} 
            placeholder="description" 
            onChange={handleChange}/>
            <button>send todo</button>
        </form>

Enter fullscreen mode Exit fullscreen mode

make the handleChange function

const handleChange = e => {
    const {name, value} = e.target
    setInputs(prev => ({...prev, [name]: value}))
  }
Enter fullscreen mode Exit fullscreen mode

lets add a the event object e for short to the sendTodo and e.preventDefault() to keep it form automatically refreshing.

the first 2 lines of the sendTodo() function should look like this.

 const sendTodo = (e) => {
    e.preventDefault()

Enter fullscreen mode Exit fullscreen mode

the new getTodos() will look like this now.

const getTodos = () => {
      firebase.db.collection('todo').get()
        .then(querySnapshot => {
        querySnapshot.forEach( doc => {
          setTodos(prev => ([...prev, doc.data()]))
        })
      })
      .catch(err => {
        console.log(err.message)
      })
    }

Enter fullscreen mode Exit fullscreen mode

this isn't best practice but it will work for learning purposes.

now call on the getTodos() in the sendTodos() under the console.log('document reference ID', documentReference.id)

the new sendTodo() will look like this.

const sendTodo = (e) => {
    e.preventDefault()
    firebase.db.collection('todo').add(inputs)
    .then( async documentReference => {
      console.log('document reference ID', documentReference.id)
      await setTodos([])
      getTodos()
    })
    .catch(error => {
      console.log(error.message)
    })
  }

Enter fullscreen mode Exit fullscreen mode

we make some async and await magic happen in the .then this is because it will duplicate state (Not firestore) everytime you add a todo. we are making this function await us setting state back to empty and only after are we repopulating state.

finding a better way to do this is an exercise I will leave to you. after you figure it out leave the technique you used in the comments.

and the whole App.js with everything working will look like this.

import React, {useEffect, useState} from 'react';
import firebase from './firebase/index'



const App = () => {
  const [todos, setTodos] = useState([])
  const initstate = { title: '', description: ''}
  const [inputs, setInputs] = useState(initstate)

    useEffect( () => {
        getTodos()


    }, [])
    const getTodos = () => {
      firebase.db.collection('todo').get()
        .then(querySnapshot => {
        querySnapshot.forEach( doc => {

          setTodos(prev => ([...prev, doc.data()]))
        })
      })
      .catch(err => {
        console.log(err.message)
      })
    }


    const sendTodo = async (e) => {
      e.preventDefault()
      await firebase.db.collection('todo').add(inputs)
      .then( async documentReference => {
        console.log('document reference ID', documentReference.id)
        await setTodos([])
     // set todos back to an empty array before re-fetching the whole db.
        getTodos()
      })
      .catch(error => {
        console.log(error.message)
      })

  }

  const handleChange = e => {
    const {name, value} = e.target
    setInputs(prev => ({...prev, [name]: value}))
  }


  return (
    <div>
      <h1>send todo</h1>
        <form onSubmit={sendTodo}>
          <input name='title'
            placeholder="title" 
            value={inputs.title}
            onChange={handleChange}/>
          <input 
            name='description'
            value={inputs.description} 
            placeholder="description" 
            onChange={handleChange}/>
            <button>send todo</button>
        </form>


         {todos.length === 0 ? <p>make  your first todo</p> : todos.map(todo => <h1 >{todo.title}</h1>) }
    </div>
  );
};

export default App;


Enter fullscreen mode Exit fullscreen mode

that's it, you now have a static website with the use of a server.

obviously there is a lot to do to make this a functional website but this was about getting started with firestore, if you want to make another CRUD app there are a lot of tutorials on how to do so. The only way you can become a better developer is by memorizing the small lego pieces and practicing how to put them together.

conclusion...

every problem you run into is an opportunity to strengthen your technical sophistication as a developer. that being said. see if you can figure out how to make this a full CRUD app without finding another tutorial.

the docs are a great resource

if you liked this article please share and subscribe. if you didn't like it please tell me why and I will improve everytime I get feedback

the github for this article.

see the working project.

thanks

💖 💪 🙅 🚩
tallangroberg
Tallan Groberg

Posted on January 13, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related