Yeoman generator for starting projects using React and Firebase (Redux optional)
Install Yeoman and generator-react-firebase using npm (we assume you have pre-installed node.js):
npm install -g yo generator-react-firebase
- Do the following in the Firebase Console:
- Create both a Firestore Database and Real Time Database within your project
- Enable Google and/or Email Sign In Methods in the Authentication tab (required to enable login/signup within your application)
-
Create a project folder and enter it:
mkdir myProject && cd myProject
-
Generate project:
yo react-firebase
(project will be named after current folder) -
Confirm dependencies are installed:
npm i && npm i --prefix functions
-
Start application:
npm start
Project will default to being named with the name of the folder that it is generated within (in this case
myProject
)
- Deploy your application either manually through firebase-tools or by setting up CI Deployment
- Enable APIs for features which were opted into:
- Checkout and understand
src/config.js
. This was generated for you for your local development environment, but is is ignored from git tracking (within.gitignore
). You can have different settings within this file based on environment or if multiple developers are running the same code. - Tryout the Sub Generators to add new features to your project quickly
- Tryout Firestore (you can generate a new project with
yes
as the answer toDo you want to use Firestore
). Things work mostly the same, but it runs throughredux-firestore
.
- React + React Dom
^16.8.1
(has hooks) - Material-UI application styling including Navbar
- Full Authentication with validation (through Email, Google or Github)
- Async route loading (using react-loadable)
- Route protection (only view certain pages when logged in)
- Firebase Functions Setup with function splitting for faster cold-starts (including support within function sub-generator)
- Account Management Page
- Automatic Build/Deploy config for multiple CI Providers including:
- Gitlab (uses pipelines)
- Travis
- Component Testing With Jest
- UI Testing with Cypress
- react - Rendering + Components
- react-router - Routing (including async route loading)
- material-ui - Google Material Styling React Components
- eslint - Linting (also implements
prettier
) - react-loadable - HOC for async route/component chunk loading
When opting into redux
- redux - Client Side state optional
- react-redux-firebase - Easily Persist results of Firebase queries to redux state
- redux-auth-wrapper - Easily create HOCs for route/component protection based on auth state
- redux-form - Form input validation + state
- redux-form-material-ui - Material UI components that work nicely with redux-form
Sub generators are included to help speed up the application building process. You can run a sub-generator by calling yo react-firebase:<name of sub-generator> <param1>
.
Example: To call the component
sub-generator with "SomeThing" as the first parameter write: yo react-firebase:component SomeThing
Another argument can be passed to sub generators (unless otherwise noted) to provide the base path in which you would like to run the generator (starts from src
). For example: yo react-firebase:component Car routes/Project
runs the component generator within the Project route folder meaning that the component will be added to routes/Project/components
instead of the top level src/components
folder.
Generates a Cloud Function allowing the user to specify trigger type (from HTTPS, Firestore, RTDB, Auth, or Storage)
A component is best for things that will be reused in multiple places. Our example
command
yo react-firebase:function uppercaser
result
/functions
--/uppercaser
----index.js
For firebase-functions >v1.0.0
:
/functions/uppercaser/index.js:
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import { to } from 'utils/async';
const eventName = 'uppercaser';
/**
* @param {functions.Event} event - Function event
* @return {Promise}
*/
async function uppercaserEvent(event) {
const { params: { pushId }, data } = event;
console.log('uppercaser onUpdate event:', data.val());
// Create RTDB for response
const ref = admin.database().ref(`responses/${eventName}/${pushId}`);
// Write data to RTDB
const [writeErr] = await to(ref.set({ hello: 'world' }));
// Handle errors writing data to RTDB
if (writeErr) {
console.error(
`Error writing response: ${writeErr.message || ''}`,
writeErr
);
throw writeErr;
}
// End function execution by returning
return null;
}
/**
* Event handler that fires every time data is updated in Firebase Realtime Database.
*
* Trigger: `RTDB - onUpdate - '/uppercaser/{pushId}'`
* @name uppercaser
* @type {functions.CloudFunction}
* @public
*/
export default functions.database
.ref(`/${eventName}/{pushId}`)
.onUpdate(uppercaserEvent);
For firebase-functions >=v1.0.0
:
/functions/uppercaser/index.js:
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { to } from 'utils/async'
const eventName = 'uppercaser'
/**
*
* @param {functions.database.DataSnapshot} snap - Data snapshot of the event
* @param {Function} snap.val - Value after event
* @param {functions.EventContext} context - Function event context
* @param {Object} context.auth - Authentication information for the user that triggered the function
* @return {Promise}
*/
async function uppercaserEvent(snap, context) {
const { params: { pushId } } = context
console.log('uppercaser onCreate event:', snap.val())
// Create RTDB for response
const ref = admin.database().ref(`responses/${eventName}/${pushId}`)
// Write data to RTDB
const [writeErr] = await to(ref.set({ hello: 'world' }))
// Handle errors writing data to RTDB
if (writeErr) {
console.error(`Error writing response: ${writeErr.message || ''}`, writeErr)
throw writeErr
}
// End function execution by returning
return null
}
/**
* Cloud Function that is called every time new data is created in Firebase Realtime Database.
*
* Trigger: `RTDB - onCreate - '/requests/uppercaser/{pushId}'`
* @name uppercaser
* @type {functions.CloudFunction}
* @public
*/
export default functions.database
.ref(`/requests/${eventName}/{pushId}`)
.onCreate(uppercaserEvent)
Note: This sub-generator does not support the Path Argument (functions are already placed within a folder matching their name).
Generates a React component along with an option matching style file (either Javascript or SCSS) and places it within /components
.
A component is best for things that will be reused in multiple places. Our example
command
yo react-firebase:component Car
result
/app
--/components
----/Car
------index.js
------Car.enhancer.js // optional
------Car.styles.js // optional (Localized MUI Styling)
------Car.js
------Car.scss // option (SCSS File)
/app/components/Car.js:
import React from 'react'
import PropTypes from 'prop-types'
import classes from './Car.scss'
function Car({ car }) {
return (
<div className={classes.container}>
<span>Car Component</span>
<pre>{JSON.stringify(car, null, 2)}</pre>
</div>
)
}
export default Car
NOTE: Option to use Javascript file for styles is only offered if @material-ui/core
is included in package.json
Generates a Redux Form wrapped React component along with a matching scss file and places it within /components
.
command
yo react-firebase:form Car
or
yo react-firebase:form CarForm
result
/app
--/components
----/CarForm
------index.js
------CarForm.enhancer.js
------CarForm.js
------CarForm.scss
/app/components/CarForm.js:
import React from 'react'
import PropTypes from 'prop-types'
import classes from './Car.scss'
function CarForm({ car }) {
return (
<div className={classes.container}>
<span>CarForm Component</span>
<pre>{JSON.stringify(car, null, 2)}</pre>
</div>
)
}
export default CarForm
Generates an enhancer for a react component. Also includes an index file that wraps the component in the enhancer.
command
yo react-firebase:enhancer Project
result
/app
--/components
----/Project
------index.js
------Project.enhancer.js
Generates a React component along with a matching component (which has an scss file, an enhancer, and its own index file).
command
yo react-firebase:route Project
result
/app
--/routes
----/Project
------index.js
------components
--------ProjectPage
----------index.js
----------Project.enhancer.js // optional
----------Project.js
----------Project.scss
Generates a React component along with a matching component (which has an scss file, an enhancer, and its own index file).
command
yo react-firebase:module notification
result
/app
--/modules
----/notification
------components
------actions.js
------actionTypes.js
------index.js
------reducer.js
Note: This sub-generator does not support the Path Argument (functions are already placed within a folder matching their name).
Project outputted from generator has a README explaining the full structure and details specific to settings you choose. This includes everything from running your code to deploying it. Some of the key pieces of that information are included below:
To add a unit test, create a .spec.js
or .test.js
file anywhere inside of src
. Jest will automatically find these files and generate snapshots to the __snapshots
folder.
Cypress is used to write and run UI tests which live in the cypress
folder. The following npm scripts can be used to run tests:
* Run using Cypress run: `npm run test:ui`
* Open Test Runner UI (`cypress open`): `npm run test:ui:open`
Build code before deployment by running npm run build
. There are multiple options below for types of deployment, if you are unsure, checkout the Firebase section.
- Install Firebase Command Line Tool:
npm i -g firebase-tools
Note: Config for this is located within
firebase-ci
has been added to simplify the CI deployment process. All that is required is providing authentication with Firebase:
- Login:
firebase login:ci
to generate an authentication token (will be used to give Travis-CI rights to deploy on your behalf) - Set
FIREBASE_TOKEN
environment variable within Travis-CI environment - Run a build on CI
If you would like to deploy to different Firebase instances for different branches (i.e. prod
), change ci
settings within .firebaserc
.
For more options on CI settings checkout the firebase-ci docs
- Run
firebase:login
- Initialize project with
firebase init
then answer:- What file should be used for Database Rules? ->
database.rules.json
- What do you want to use as your public directory? ->
build
- Configure as a single-page app (rewrite all urls to /index.html)? ->
Yes
- What Firebase project do you want to associate as default? -> your Firebase project name
- What file should be used for Database Rules? ->
- Build Project:
npm run build
- Confirm Firebase config by running locally:
firebase serve
- Deploy to Firebase (everything including Hosting and Functions):
firebase deploy
NOTE: You can use firebase serve
to test how your application will work when deployed to Firebase, but make sure you run npm run build
first.
Complete examples of generator output available in Examples
- react-firebase-redux example -
redux
and Firebase Real Time Database - redux-firestore -
redux
and Firestore - react-firebase example - Firebase Real Time Database
For full projects built out using this as a starting place, check the next section.
- fireadmin.io - Application for Managing Firebase Applications. Includes support for multiple environments and data migrations.
- devshare.io - Codesharing site based on Firebase's Firepad and Realtime Database
- A number of projects at Reside
- react-redux-firebase material example - Shows usage of react-redux-firebase with material-ui
- react-redux-firebase firestore example - Shows usage of react-redux-firebase with firestore
open an issue or reach out over gitter if you would like your project to be included
-
Why node
8
instead of a newer version? Cloud Functions runtime is still on8
, which is why that is what is used for the suggested build version as well as the version used when building within CI. -
How do I deploy my application? The README of your generated project specifies deployment instructions based on your choices while generating. For an example, checkout any of the
README.md
files at the root of projects in the examples folder including this one. -
How do I add a route?
- Use the route sub-generator to add the route:
yo react-firebase:route MyRoute
- Add a
path
of the new route toconstants/paths
(i.e.MYROUTE_PATH
) - Add the route to the list of routes in
src/routes/index.js
- Use the route sub-generator to add the route:
-
Why
enhancers
overcontainers
? - For many reasons, here are just a few:- separates concerns to have action/business logic move to enhancers (easier for future modularization + optimization)
- components remain "dumb" by only receiving props which makes them more portable
- smaller files which are easier to parse
- functional components can be helpful (along with other tools) when attempting to optimize things
-
Where are the settings for changing how my project deploys through Continious integration?
Within
.firebaserc
under theci
section. These settings are loaded by firebase-ci -
How does one override
react-redux-firebase
andredux-firestore
configuration based on the environment? Like adding logging only to staging?Add the following to
.firebaserc
under the branch associated with the environment you wish to change:"reduxFirebase": { "userProfile": "users", "enableLogging": false }
Should look end up looking similar to the following:
"ci": { "copyVersion": true, "createConfig": { "master": { "env": "staging", "firebase": { "apiKey": "${STAGE_FIREBASE_API_KEY}", "authDomain": "some-project.firebaseapp.com", "databaseURL": "https://some-project.firebaseio.com", "projectId": "some-project", "storageBucket": "some-project.appspot.com" }, "reduxFirebase": { "userProfile": "users", "enableLogging": true } } } }
-
Why are there
__snapshots__
folders everywhere when opting into Jest?Jest just recently added support for adding your own snapshot resolver that allows you to place the
__snapshots__
folder at the top level (logic included inscripts/snapshotResolver.js
). Since feature is still in alpha, it is not yet included with this generator. While testing supporting a top level__snapshots__
folder, there were a number of issues, but the provided resolver did work as expected in some cases.I got it working by:
- Ejecting result of generator (
yarn eject
) - Installing beta version of Jest that is at least
24.0.0-alpha.6
-yarn add jest@beta --dev
- Adding a snapshot resolver to place snapshots where you want as
scripts/snapshotResolver.js
- Referencing the snapshot resolver reference within
package.json
(which should contain jest config after ejecting):"snapshotResolver": "<rootDir>/scripts/snapshotResolver.js"
- Ejecting result of generator (
-
How do I move/rename the
cypress
folder to something more general? If you wanted to move thecypress
folder intotest/ui
for intance, you could modify yourcypress.json
file to match the following:cypress.json
{ "chromeWebSecurity": false, "fixturesFolder": "test/ui/fixtures", "integrationFolder": "test/ui/integration", "pluginsFile": "test/ui/plugins/index.js", "screenshotsFolder": "test/ui/screenshots", "videosFolder": "test/ui/videos", "supportFile": "test/ui/support/index.js" }
-
Some of my answers were saved, how did that happen? Why?
Yeoman has the
store
option, which uses the Yeoman Storage API to store answers to questions within within a.yo-rc.json
. This allows you to rerun the generator in the future to recieve updates without having to remember the answers you used or re-lookup them up.This also shows you how examples were done by answering the generator questions.
-
How can I extend the build settings of my app after generating?
There are two options:
- Use
npm run eject
oryarn eject
to eject from react-scripts (this cannot be undone) - Use
customize-cra
andreact-app-rewired
to modify settings without ejecting (for more see the question about not ejecting)
- Use
-
How do I extend the webpack/babel config without ejecting?
-
Install
customize-cra
andreact-app-rewired
:npm i --save-dev customize-cra react-app-rewired
-
Add the following to the
scripts
section of yourpackage.json
file:"start": "react-app-rewired start", "build": "react-app-rewired build", "eject": "react-app-rewired eject", "test": "react-app-rewired test"
-
Add
config-overrides.js
that looks like so (any utils can be used inoverride
):const utils = require('customize-cra'); module.exports = utils.override( utils.disableEsLint(), utils.useBabelRc() );
-
-
What happened to the
scss
from before? What if I want to do the same setup?It was removed in favor of Javascript styling through
*.styles.js
files. It is common to use Javascript styles with material-ui, so following this pattern allows mirrors their examples/docs.If you want to do the same setup as before, make sure you reference the scss files correctly (now that the build config is through
react-scripts
). For example if you want to importstyles/_base.scss
make sure you switch your imports like the following:- @import 'base'; + @import 'styles/_base.scss';
- Airbnb linting option (currently only
standard
) - Option to use simple file structure instead of fractal pattern
- Open to ideas
MIT © Prescott Prue