React with Firebase: Firestore setup
Tallan Groberg
Posted on January 13, 2020
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.
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.
name your project.
select your default account.
click where it says database.
click where it says to make a database.
start in test mode and once everything is working change back to production mode.
choose a location. Ideally where your users are likely to be and not necessarily where you are.
next you need to register your app.
name your app.
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
cd into the app
cd firestore-tutorial
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
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
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.
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
}
now import firebase into the App.js at the top of the file like so.
import firebase from './firebase/index'
then inside the App component add a console log
console.log(firebase)
open the dev tools and you should see your config object.
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)
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({})
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)
})
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;
go to localhost:3000 and click the button to send todo.
this will give you a document reference id.
if you go back to your firebase console and click on database.
You will notice that some changes have occurred.
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';
make the piece of state at the top of the component.
const [todos, setTodos] = useState([])
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)
})
}
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>) }
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;
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)
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>
make the handleChange function
const handleChange = e => {
const {name, value} = e.target
setInputs(prev => ({...prev, [name]: value}))
}
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()
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)
})
}
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)
})
}
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;
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
see the working project.
thanks
Posted on January 13, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.