How to use React with Firebase and Redux
By: Nazare Emanuel Ioan
Tags: Programming, Tech, Redux, React, Design Systems

As we can see, they have a name, a stat, an icon (that varies in color), a footer icon and percentage (that once again, varies in color) and a footer text.

So, we’ll need to create the action that will accept all of the above, like so:

const addStatCardAction = (

statName,

statDescription,

statIcon,

statIconColor,

statFooterIcon,

statFooterIconState,

statFooterPercentage,

statFooterText

) => async dispatch => {

// here we'll make a call to our database (firebase)

// to add our new stat card with the above details dispatch({

type: "addStatCard",

payload: {

statName: statName,

statDescription: statDescription,

statIcon: statIcon,

statIconColor: statIconColor,

statFooterIcon: statFooterIcon,

statFooterIconState: statFooterIconState,

statFooterPercentage: statFooterPercentage,

statFooterText: statFooterText

}

});

}; export default addStatCardAction;

As we can see, we’re going to work with async action creators, since we are making calls to a database. After the call is done, we’ll need to send to our store the data that we’ve just added to our database in firebase.

src/actions/getAllStatCardsAction.js

This one will not require any params, since it only retrieves something from the database. So the code will look like this:

const getAllStatCardsAction = () => async dispatch => {

// here we'll make a call to our database (firebase)

// that will retrieve all of our stat cards dispatch({ type: "getAllStatCards" , payload: {}});

}; export default getAllStatCardsAction;

src/actions/loginAction.js

For login, we’ll have an email and a password, so this is the code for this action (also our login form has an email and a password):

const loginAction = (email, password) => async dispatch => {

// at the moment, since we haven't yet connected to the database

// we are going to say that each time we try to login

// we should not be able to log in (that is why we send false) dispatch({ type: "login", payload: false });

}; export default loginAction;

src/actions/registerAction.js

const registerAction = (name, email, password) => async dispatch => {

// at the moment, since we haven't yet connected to the database

// we are going to say that each time we try to register

// we should not be able to register (that is why we send false) dispatch({ type: "register", payload: false });

}; export default registerAction;

Reducers

src/reducers/statCardReducer.js

Since we have two actions about the stat card, we’ll have two cases in this reducer:

export default (state = {}, action) => {

switch (action.type) {

case "addStatCard":

console.log("adding ", action.payload);

// since we will always fetch our stat cards

// from firebase, each time we add one new

// we will just return the state

return state;

case "getAllStatCards":

console.log("getting ", action.payload);

console.log(action.payload);

return {

// keep the old state

...state,

// add all the cards from the database

// they will come in a json format,

// so we need to convert them to array

statCardState: Object.values(action.payload)

};

default:

return state;

}

};

We’re also logging what we are adding and what we are trying to get from our firebase.

src/reducers/authReducer.js

export default (state = {}, action) => {

switch (action.type) {

// in both cases, we want to tell our app,

// if the user is logged in or not

// if the user registers, he will automatically be logged in case "register":

console.log("register is ",action.payload);

return {

// keep old state

...state,

// add true/false if the user is or not logged in

loggedIn: action.payload

};

case "login":

console.log("login is ",action.payload);

return {

// keep old state

...state,

// add true/false if the user is or not logged in

loggedIn: action.payload

};

default:

return state;

}

};

When we register a new user, we’ll automatically log them in. We’ve also added some logs to see if the registration or the login is successful.

src/reducers/rootReducer.js

This is for combining the above reducers:

import { combineReducers } from "redux"; import authReducer from "reducers/authReducer";

import statCardReducer from "reducers/statCardReducer"; export default combineReducers({

// the authReducer will work only with authState

authState: authReducer,

// the statCardReducer will work only with statCardState

statCardState: statCardReducer

});

Store

src/store.js

Since we have async action creators, we’ll need a middleware that will allow us to use these actions creators, hence the usage of redux-thunk:

import { createStore, applyMiddleware } from "redux";

import reduxThunk from "redux-thunk"; import rootReducer from "reducers/rootReducer"; function configureStore(

state = { authState: {}, statCardState: {} }

) {

return createStore(rootReducer, state, applyMiddleware(reduxThunk));

} export default configureStore;

Connecting our app to our store

At the moment, if we were to start our app, nothing would happen, since all the actions and our store are not being rendered in our app. So this is what we are going to do now.

First, let’s add our store, for this, we need to fo inside src/index.js.

Before the ReactDOM.render() function we need to add the following imports:

import { Provider } from "react-redux";

import configureStore from "store";

And after that, we’ll wrap the BrowserRouter from the ReactDOM.render() function inside the Provider tag as follows:

<Provider store={configureStore()}>

<BrowserRouter>

<Switch>

<Route path="/admin" render={

props => <AdminLayout {...props} />

} />

<Route path="/auth" render={

props => <AuthLayout {...props} />

} />

<Redirect from="/" to="/admin/index" />

</Switch>

</BrowserRouter>

</Provider>,

Our next concern is to make our users to be redirected to the login page if not authenticated and if they are authenticated to be redirected to the user page. Basically, if they are logged in, they will not be able to access the Auth layout (src/layouts/Auth.jsx), and if they are not, they won’t be able to access the Admin layout (src/layouts/Admin.jsx).

Let’s go inside src/layouts/Auth.jsx and after the React import, make the following imports:

import { connect } from "react-redux";

import { Redirect } from "react-router-dom";

After that let’s change the export of this component as follows:

const mapStateToProps = state => ({

...state

}); export default connect(

mapStateToProps,

{}

)(Auth);

After this, we go inside the render function of this component, and before the return, add the following code:

if (this.props.authState.loggedIn) {

return <Redirect to="/admin/user-profile" />;

}

So, if the user is authenticated, they will be redirected to their profile page.

Next, we go inside src/layouts/Admin.jsx and make the same changes as with the Auth layout. So add the following imports:

import { connect } from "react-redux";

import { Redirect } from "react-router-dom";

Change it’s export to:

const mapStateToProps = state => ({

...state

}); export default connect(

mapStateToProps,

{}

)(Admin);

Once again, in the render function, before the return we add:

if (!this.props.authState.loggedIn) {

return <Redirect to="/auth/login" />;

}

This time, we say !this.props.authState.loggedIn, since we want the user to be redirected to the login page if they are not authenticated.

Let us start again our project and see how, each time if we try to navigate to the Dashboard or Profile, we are not allowed since we are not logged in.

Project after adding the redirects

Now, we need to go inside the Login and Register view-pages and add Redux to them as well.

Connecting our Login page to redux using loginAction

First, let's go inside src/views/examples/Login.jsx and after the React import, add these imports:

import { connect } from "react-redux"; import loginAction from "actions/loginAction";

Then, change the export at the end of the file with this:

const mapStateToProps = state => ({

...state

}); const mapDispatchToProps = dispatch => ({

loginAction:

(email, password) => dispatch(loginAction(email, password))

}); export default connect(

mapStateToProps,

mapDispatchToProps

)(Login);

Now, before the render function we write:

state = {

email: "",

password: ""

};

onChange = (stateName, value) => {

this.setState({

[stateName]: value

});

};

We’ll need to keep a local state for the email and password and send these two to our firebase.

Then, we need to change line 85 from:

<Input placeholder="Email" type="email" />

To:

<Input

placeholder="Email"

type="email"

onChange={e => this.onChange("email", e.target.value)}

/>

We’ll also change line 99 from:

<Input placeholder="Password" type="password" />

To:

<Input

placeholder="Password"

type="password"

onChange={e => this.onChange("password", e.target.value)}

/>

We’re almost set for the login. Next we need to change the Sign in button so that, when we press it, it will call the loginAction. So change it from:

<Button className="my-4" color="primary" type="button">

Sign in

</Button>

To:

<Button

className="my-4"

color="primary"

type="button"

onClick={() =>

this.props.loginAction(

this.state.email,

this.state.password

)

}

>

Sign in

</Button>

Now go back in your browser, and on the Login page, open your console, and try to log in. You should get an output of login is false. So we know that our action and our reducer work.

login is false

Connecting our Register page to redux using registerAction

Go inside src/views/examples/Register.jsx and do the same as the above. So first add the imports (this time with the registerAction):

import { connect } from "react-redux"; import registerAction from "actions/registerAction";

Then, the export to:

const mapStateToProps = state => ({

...state

}); const mapDispatchToProps = dispatch => ({

registerAction: (name, email, password) => dispatch(registerAction(name, email, password))

}); export default connect(

mapStateToProps,

mapDispatchToProps

)(Register);

Add the following before the render function:

state = {

name: "",

email: "",

password: ""

};

onChange = (stateName, value) => {

this.setState({

[stateName]: value

});

};

Change:

<Input placeholder="Name" type="text" />

To:

<Input placeholder="Name" type="text" onChange={e => this.onChange("name", e.target.value)}/>

Then:

<Input placeholder="Email" type="email" />

To:

<Input placeholder="Email" type="email" onChange={e => this.onChange("email", e.target.value)}/>

And lastly, the password as well:

<Input placeholder="Password" type="password" />

To:

<Input placeholder="Password" type="password" onChange={e => this.onChange("password", e.target.value)}/>

One more thing — the button, we need to change it from:

<Button className="mt-4" color="primary" type="button">

Create account

</Button>

To:

<Button className="mt-4" color="primary" type="button"

onClick={() =>

this.props.registerAction(

this.state.name,

this.state.email,

this.state.password

)

}>

Create account

</Button>

So, we are all set with Redux. Again, go to the Register page, type something inside the form, and then press the Create account button with the console opened. You should get a register is false.

register is false

Connecting our Header component to redux using addStatCardAction and getAllStatCardsAction actions

Now we need to make our Stat Cards from the Header component (this component can be seen for example inside the Dashboard page) to be rendered from our store/firebase, and also, make them create dynamically — for example on a button click.

Go inside src/components/Headers/Header.jsx and add the following imports (after the React import):

import {connect} from "react-redux"; import addStatCardAction from "actions/addStatCardAction";

import getAllStatCardsAction from "actions/getAllStatCardsAction"; import { Button } from "reactstrap";

Change the default export to:

const mapStateToProps = state => ({

...state

});

const mapDispatchToProps = dispatch => ({

getAllStatCardsAction: () => dispatch(getAllStatCardsAction()),

addStatCardAction: (

statName,

statDescription,

statIcon,

statIconColor,

statFooterIcon,

statFooterIconState,

statFooterPercentage,

statFooterText

) =>

dispatch(

addStatCardAction(

statName,

statDescription,

statIcon,

statIconColor,

statFooterIcon,

statFooterIconState,

statFooterPercentage,

statFooterText

)

)

}); export default connect(

mapStateToProps,

mapDispatchToProps

)(Header);

Then, let’s add a componentDidMount function right before the render one as follows:

componentDidMount(){

this.props.getAllStatCardsAction();

}

And now, after the first div inside the return statement of the render function, we’ll add a Button that will add our stat cards inside our firebase:

<Container>

<Row>

<Col lg="6" xl="3">

<Button

color="primary"

onClick={() =>

this.props.addStatCardAction(

"Performance",

"49,65%",

"fas fa-percent",

"bg-info text-white rounded-circle shadow",

"fas fa-arrow-up",

"text-success",

" 12%",

"Since last month"

)

}

>

Add stat card

</Button>

</Col>

</Row>

</Container>

<br />

And, we now need to delete the whole contents of the Row tag (~lines 48–165 — from <Row> to </Row>), and replace it with the following:

{// we first verify if the statCardState is undefined

this.props.statCardState &&

// then verify if the statCardState.statCardState is

// populated with cards from our firebase

this.props.statCardState.statCardState &&

// and lastly, we render them using the map function

this.props.statCardState.statCardState.map((prop, key) => {

return (

<Col lg="6" xl="3" key={key}>

<Card className="card-stats mb-4 mb-xl-0">

<CardBody>

<Row>

<div className="col">

<CardTitle

tag="h5"

className="text-uppercase text-muted mb-0"

>

{prop.statName}

</CardTitle>

<span className="h2 font-weight-bold mb-0">

{prop.statDescription}

</span>

</div>

<Col className="col-auto">

<div

className={

"icon icon-shape " + prop.statIconColor

}

>

<i className={prop.statIcon} />

</div>

</Col>

</Row>

<p className="mt-3 mb-0 text-muted text-sm">

<span

className={"mr-2 " + prop.statFooterIconState}

>

<i className={prop.statFooterIcon} />{" "}

{prop.statFooterPercentage}

</span>{" "}

<span className="text-nowrap">

{prop.statFooterText}

</span>

</p>

</CardBody>

</Card>

</Col>

);

})}

Adding Firebase

Setting Firebase Account

For this, you need to have a Google Account. If you do not have one, Google offers you a fast (1 minute) Guide.

After you’ve made your account, sign into it, or if you have one, sign into that one.

After that, navigate to this page (this is the homepage of firebase) and press the GO TO CONSOLE button, or just navigate directly to this link.

After that press on the Add project button. You will be prompted with a modal, with an input for a name (you can type whatever name you would like). For me, it will be react-redux-firebase-tutorial. You can leave everything else as is. Accept the terms and then press the Create Project button. You’ll have to wait a bit until it creates the project (around 30 seconds).

After that press the Continue button. That will automatically redirect you to the new project page. In the left menu press the Authentication link. On that press the Set up sign-in method. You will have a table with Provider and Status. Press on the line Email/Password. And check the first Switch and then press the Save button.

Now, go to Database link, scroll down the page and press Create database button, under the Realtime Database. After this, on the modal prompt that opens, choose Start in test mode radio and then press Enable and wait a few seconds.

Next, you’ll need to get your config file (config file that we will add it to our project in the next section). For this press on Project Overview link in the left menu, and after that press on the </> (Web) button. Copy the config variable and the firebase initialization. We’ll paste this in a new file, in the next section.

We are done!

We won’t need to create any tables for our users, our users’ details, or our dynamic cards, since firebase will automatically create them — we’ll talk about this in the next section.

Here are the above steps, as images: