Building a SAAS App with React, Redux and Firebase - Part 1

We've been exporing different Backend-As-A-Service (BAAS) options and decided to publish a short series on how to build a simple Software-As-A-Service (SAAS) Application using React, Redux and Firebase.

We're going to be using Firebase's different services to all parts of the Application we build including Auth, Functions, and Hosting.

The series is based on the assumption that you have a working knowledge of React and Redux and therefore won't drill into too much detail about every step. It's purpose is to be a simple guide to help you get started on a similar project.

There is a lot to go over so let's start from scratch and build our way up to a complete application.

Source Code

https://github.com/devato/react-redux-firebase

The Plan - Part 1

  • Start from scratch with Create React App
  • Install React Router
  • Create Root Component to configure redux store
  • Create main index entrypoint
  • Create App component
  • Create Home component
  • Create Posts component
  • Set up Redux and associated packages
  • Create firebase project
  • Configure Firebase in application

Start from scratch with Create React App

Assuming you have create-react-app installed (we're using version 2.1.1),

Create a new app:

$ create-react-app react-redux-firebase && cd react-redux-firebase

This will create an app named react-redux-firebase and change into the new directory.

Next, let's delete all the files in the src directory so we can start from scratch.

In order to follow along, delete all the files in the src directory

In an effort to eliminate using multiple relative path on our import statements, we're going to adjust the scripts section of package.json to set a NODE_PATH environment variable. Our system will use include this as a starting point when searching for files to import.

Open package.json and update the scripts section like this:

  "scripts": {
    "start": "NODE_PATH=./src react-scripts start",
    "build": "NODE_PATH=./src react-scripts build",
    "test": "NODE_PATH=./src react-scripts test",
    "eject": "NODE_PATH=./src react-scripts eject"
  },

Now all our import statement can just start with the parent directory under src.

Install React Router

We're going to use react-router-dom to handle our client side routing.

Install react-router-dom:

$ yarn add react-router-dom

Create Root Component

We're going to create a bunch of foundational components in the next few steps, but just follow along and skim through the code to understand the set up.

Create the file src/Root.jsx with the following content:

import React from 'react'

export default ({ children, initialState = {} }) => {
  return (
    <div>{children}</div>
  )
}

This will serve as a wrapper around the core application where we will handle our redux configuration. children is a special prop from React that contains arbitary elements nested in the JSX. This will become more clear when we build out or main components.

We'll expand on this in a little while, so let's leave it like this for now.

Create index.js entrypoint

Next, we'll create a new src/index.js that will glue our Root component and the rest of the application together.

Create src/index.js with this content:

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter, Route } from 'react-router-dom'
import App from 'components/App'
import Root from 'Root'

ReactDOM.render(
  <Root>
    <BrowserRouter>
      <Route path="/" component={App} />
    </BrowserRouter>
  </Root>
  ,
  document.getElementById('root')
)

This imports the router and App and Root components and organizes them for our main App component to load on the "/" index path, then mounts react to the root element found in public/index.html.

We're not going into detail about how React mounts onto and html element, but feel free to look this up yourself.

Notice also that because our NODE_PATH is set to src, we can import files relative to the src directory without the need for too many relative paths: ../

Create App Component

Next let's create our App component in a components directory. For simplicity, we're not going to use the container pattern of organizing our components, components will just be considered components.

Create src/components/App.jsx with the following content:

import React from 'react'
import { Route } from 'react-router-dom'
import Home from 'components/Home'
import Posts from 'components/Posts'

class App extends React.Component {
  render() {
    return (
      <div>
        <Route exact path="/" component={Home} />
        <Route path="/posts" component={Posts} />
      </div>
    )
  }
}

export default App

The App component is where we'll manage our routes and determine what to render. Notice we're importing to components: Home and Posts.

Our Home component will be public, not requiring any authorization, but the Posts component will. We're still just doing some initial set up, so let's create these components next.

Create Home Component

Our home route will not need authorization to access, and will only render public elements. Let's create a Home directory to hold public components:

Create src/components/Home/index.js with the following content:

import React from 'react'

class Home extends React.Component {
  render() {
    return (
      <div>
        this is home
      </div>
    )
  }
}

export default Home

For now it's just a placeholder to let us know we're on the home page. We'll expand on this component later.

Create Posts Component

We're also going to organize our Posts components into a Posts directory where index.jsx will be the Posts index.

Posts will eventually be private where we'll require users to be registered or authed to access them.

For now, create src/Posts/index.jsx with this content:

import React from 'react'

class Posts extends React.Component {
  render() {
    return (
      <div>this is posts index</div>
    )
  }
}

export default Posts

That was a lot of initial setup, but as we progress through the series, this organization of components will start to make more sense.

Start Server and Browser Test

We can now fire up the development server and test this puppy out.

Run:

$ yarn start

If everything went well, you should see this is home in the browser at http://localhost:3000

If you go to http://localhost:3000/posts you should see this is posts index. Great, everything is starting to come together.

Before we start fleshing out up the applications features, let's first wire up redux and configure the store.

Set up Redux and Associated Packages

There are a few different packages we're going to add:

  • redux - Redux core library
  • react-redux - Official React bindings for Redux
  • redux-thunk - popular middleware for performing async logic in action creators
  • redux-logger - middleware for logging redux actions to the console

Install these packages by running:

$ yarn add redux react-redux redux-thunk redux-logger

Let's update our Root component to connect to the redux store.

Open src/Root.jsx and replace it's contents with this:

import React from 'react'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import reducers from 'reducers'
import reduxThunk from 'redux-thunk'
import reduxLogger from 'redux-logger'

export default ({ children, initialState = {} }) => {
  const store = createStore(
    reducers,
    initialState,
    applyMiddleware(reduxThunk, reduxLogger)
  )
  return (
    <Provider store={store}>
      {children}
    </Provider>
  )
}

Skim through the code and you'll notice that we're importing a Provider wrapper for react and wrapping the nested children with it, and some core methods from redux to create the store and adding reduxThunk and reduxLogger to the middleware.

We're also importing a our reducers from the reducers directory to configure the store.

Hopefully you already have an understanding of how these tools work together, but if not spend some time reading about redux, reducers, actions, and middleware.

We don't have any reducers yet, so we'll start to configure everything we need to complete our redux set up.

Create reducers

Create a new file src/reducers/index.js with the following content:

import { combineReducers } from 'redux'
import authReducer from 'reducers/auth'

export default combineReducers({
  auth: authReducer
})

This file serves as our root reducer and will combine reducers for our store. We're importing an auth reducer, but as we add more features we'll need more reducers.

Let's create the auth reducer now. Create a new file src/reducers/auth.js with the following content:

import { CHANGE_AUTH } from 'actions/types'
export default (state = false, action) => {
  switch (action.type) {
    case CHANGE_AUTH:
      return action.payload
    default:
      return state
  }
}

This reducer has a single case: CHANGE_AUTH. You can also see that we're import the constant from the file actions/types.js.

Using constants for action types helps to prevent duplication and typos that could lead to strange side effects as the application grows.

Create the file src/actions/types.js and add the constant from the auth reducer:

export const CHANGE_AUTH = 'CHANGE_AUTH'

Once this is complete, test the application in the browser and ensure that everything is still running as expected.

We're not yet using redux for anything, but we know have much of the boilerplate code in place, and we're in a good postion to start developing some features.

Create a Firebase Project

Login to the firebase console and add a new project:

Screen-Shot-2018-11-07-at-1.03.00-PM

On the popup, enter a project name, we're going to use react-redux-firebase, check the boxes to agree to the terms and hit Create Project.

Screen-Shot-2018-11-07-at-1.05.30-PM

Now your project is ready, and you should have access to the required configuration data.

On the project dashboard, under Develop -> Authentication, hit the Sign in Method tab, and enable Email/Password:

Screen-Shot-2018-11-07-at-1.16.27-PM

Now you can hit the "Web Setup" button to access your API credentials, and we have everything we need to configure firebase in our application.

Configure Firebase in application

First we'll need the firebase package:

$ yarn add firebase

Then we'll add our API credentials to .env.local:

Create .env.local in your root directory and add this content:

REACT_APP_FIREBASE_API_KEY=yourkey
REACT_APP_FIREBASE_AUTH_DOMAIN=yourdomain
REACT_APP_FIREBASE_DATABASE_URL=yoururl
REACT_APP_FIREBASE_PROJECT_ID=yoruid
REACT_APP_FIREBASE_STORAGE_BUCKET=yourbucket
REACT_APP_FIREBASE_SENDER_ID=yoursenderid

Ensure that you update the values to match those found in your firebase project dashboard.

Next, we'll create a utility file to configure and instantiate firebase.

Create src/utils/firebase.js with the following content:

import firebase from 'firebase/app'
import 'firebase/app'
import 'firebase/auth'
import 'firebase/firestore'

const config = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID,
}

const firebaseApp  = firebase.initializeApp(config)

export default firebaseApp

And finally, we'll update our App component to make use of the firebaseApp utility. Open src/components/App.jsx and update the content to this:

import React from 'react'
import { Route } from 'react-router-dom'
import { connect } from 'react-redux'
import firebaseApp from 'utils/firebase'

import Home from 'components/Home'
import Posts from 'components/Posts'

class App extends React.Component {

  componentWillMount() {
    firebaseApp.auth().onAuthStateChanged(user => {
      if (user) {
        console.log('logged in')
      } else {
        console.log('not logged in')
      }
    })
  }

  render() {
    return (
      <div>
        <Route exact path="/" component={Home} />
        <Route path="/posts" component={Posts} />
      </div>
    )
  }
}

export default connect(null)(App)

The changes above pull in our firebaseApp to check if a user is authed whenever the App mounts. For now it will just log to the console the current auth state.

Notice we've also connected our App component to the redux store. This will enable us to start dispatching actions and injecting state into our application.

Wrap up

We've covered alot of boilerplate code and configuration in Part 1 of this series, and as you can probably sense, we're getting very close to being able to start on the major features.

To recap, here's what we've accomplished:

  • Start from scratch with Create React App
  • Install React Router
  • Create Root Component to configure redux store
  • Create main index entrypoint
  • Create App component
  • Create Home component
  • Create Posts component
  • Set up Redux and associated packages
  • Create firebase project
  • Configure Firebase in application

In the next section we'll continue by creating some new components and integrated the auth features for our application.

As always, if you have any questions or feedback, leave them in the comments or follow me on twitter for updates: @tmartin8080.

Thanks for reading, and I hope you found part 1 helpful.

Series:

Source code

https://github.com/devato/react-redux-firebase