I recently started to get on the React train and what better way to jump in than following some courses and building an app. Since everything is moving into cloud these days, I also wanted to try this thing called Firebase🔥. I heard a lot about it and it seemed like a good time to use it.
In order to keep this article relatively short, I will explain the base setup and how everything ties together. I will create a trimmed version of the app to explain the basics. You can find the full project on my Github.
Getting Started
Being an Angular developer the first thing I looked for was an alternative to the angular cli environment so that I get some fast boilerplate setup.
I quickly found Create React App. The only prerequisite that I needed was to make sure that I had npm 5.2+
and Node 8.10+
installed. You can check the versions using npm -v
and node -v
commands.
Then run the next command at your desired location and replace my-app with your app name. I will call it meeting-list.
npx create-react-app my-app
⚠ Note: If your username contains spaces npx will fail to create a directory -> Github issue link. In this case you will need to install create-react-app globally then run the command.🙄
npm install -g create-react-app
create-react-app my-app
Now that we got that out of the way, we can test the app to make sure it's working. Change the directory to point to your app using cd my-app
then run the command npm run start
. If everything went well a browser tab will open at the location(if something else is not running on that port) http://localhost:3000/
and we can see our app. Yaay!
SSStyle with AntDesign
Cool cool, we got an app working, but it lacks style. Let's add some.
Stop the development server, if it's running(Ctrl+C
). Run the next command to install antd React UI library.
npm install antd --save
Now that the library is installed we need to import the base css files. Let's open the index.js
file from under the src
folder.
Paste the following line before the import of './index.css'
.
import "antd/dist/antd.css";
Aand we finished the setup of Antd. Eazy! 👌
🔥 Firebase integration
Before we start to create some base components and interractions we need to set up Firebase.
The first step is to add the firebase package to our project.
npm install firebase --save
Next we need a Firebase account. Since Firebase is owned by Google we can create one fast using a Google email.
- Navigate to Firebase and create an account
- Go to Firebase Console
- Select Add project
- Enter a project name
- (Optional) - Enable analytics
After the project is created, click on it(this will open the project console).
- Click on Add app , select the Web icon and enter a name for your app then Register app
- Copy the content of the
firebaseConfig
object
Let's create a new directory under src
and call it components
. Here I'm going to create a new file and name it Firebase.js
. This is where we will keep our firebase config.
Open Firebase.js
and add the following lines of code. Paste inside of firebaseConfig
the config object received from the firebase console for your project.
import firebase from 'firebase/app';
import 'firebase/database';
import 'firebase/auth';
// Your web app's Firebase configuration
const firebaseConfig = {
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
export const provider = new firebase.auth.GoogleAuthProvider();
export const auth = firebase.auth();
export default firebase;
⚠ Note: If you decide to publish this to Github or something similar make sure to delete the content of the firebaseConfig
object.
Enable Firebase Authentication
- In the Firebase console for your project, navigate to Develop > Authentication > Sign-in method
- Select Email/Password and toggle the Enable switch
- Be amazed how easy it was🤯
Create a registration form
Now that we enabled authentication in Firebase, we need to create a registration form in our app. In order to register we will need an email and a password.
Create a new component inside the components
folder and call it Register.js
.
Since we are using ant design we will import some UI components and firebase from our previous created Firebase.js
import React, { Component } from 'react';
import { Card, Col, Row, Form, Input, Button } from 'antd';
import firebase from './Firebase';
class Register extends Component {
}
export default Register;
Next let's add a constructor, pass some props and set a state object that will be used for the registration form, along with binding some methods.
constructor(props) {
super(props);
this.state = {
displayName: '',
email: '',
password: '',
confirmPassword: '',
errorMessage: null
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
Our next step is to create the render method and add a form. For customization we will use some antdesign components:
render() {
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 4 }
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 }
}
}
return (
<Row gutter={16}>
<Col span={12}>
<Card title="Register" bordered={true}>
{this.state.errorMessage !== null ? (<div> {this.state.errorMessage} </div>) : null}
<Form {...formItemLayout} onSubmit={this.handleSubmit}>
<Form.Item label="Display Name">
<Input name="displayName" value={this.state.displayName} onChange={this.handleChange} />
</Form.Item>
<Form.Item label="Email Address">
<Input name="email" value={this.state.email} onChange={this.handleChange} />
</Form.Item>
<Form.Item label="Password">
<Input.Password name="password" value={this.state.password} onChange={this.handleChange} />
</Form.Item>
<Form.Item label="Confirm Password">
<Input.Password name="confirmPassword" value={this.state.confirmPassword} onChange={this.handleChange} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Register
</Button>
</Form.Item>
</Form>
</Card>
</Col>
</Row>
);
}
I've left the two methods at the end since we need to explain them in more detail.
handleChange
What we want to accomplish with this method is to grab the values from our form and set in the state object our field value. We also added a check to determine if our two passwords fields match. If they do not match we set an error message that will be displayed at the top of our form.
handleChange(e) {
const itemName = e.target.name;
const itemValue = e.target.value;
this.setState({ [itemName]: itemValue }, () => {
if (this.state.password !== this.state.confirmPassword) {
this.setState({ errorMessage: 'Password do not match!' });
} else {
this.setState({ errorMessage: null });
}
});
}
handleSubmit
Here we want to call the Firebase API to authenticate our user with an email and a password. Since the createUserWithEmailAndPassword
returns a promise, after the user is registered we call a method passed through props that will log the user in our app(we will add it shortly). We also implemented a catch to handle authentication errors.
handleSubmit(e) {
const registrationInfo = {
displayName: this.state.displayName,
email: this.state.email,
password: this.state.password
}
// Stop the default behaviour of the form
e.preventDefault();
// Create a new user with email and password
firebase.auth().createUserWithEmailAndPassword(registrationInfo.email, registrationInfo.password)
.then(() => {
this.props.registerUser(registrationInfo.displayName);
}).catch(error => {
if (error.message !== null) {
this.setState({ errorMessage: error.message });
} else {
this.setState({ errorMessage: null });
}
});
}
The whole creation of a new user is in this line
firebase.auth().createUserWithEmailAndPassword(email, password)
🤯.
The rest of the code is added there for error handling and a nicer user experience.
Now that we finished with our registration component let's go back to our App.js
file and connect it.
But first... let's install another package 😆
Enter Reach Router. This package will help us navigate through multiple pages with a simple to use API.
npm install @reach/router --save
With this package installed let's create first a navigation component to quickly change between pages.
Create a new Navigation.js
file inside the components folder.
import React, { Component } from 'react';
import { Layout, Menu } from 'antd';
import { Link } from '@reach/router';
const { Header } = Layout;
class Navigation extends Component {
render() {
const { user, logOutUser } = this.props;
return (
<Header>
<Menu theme="dark" mode="horizontal" defaultSelectedKeys={['1']} style={{ lineHeight: '64px', float: 'right' }} >
{!user &&
<Menu.Item key="1">
<Link to="/login">Log in</Link>
</Menu.Item>}
{!user &&
<Menu.Item key="2">
<Link to="/register">Register</Link>
</Menu.Item>}
{user &&
<Menu.Item key="3">
<Link to="/login" onClick={e => logOutUser(e)}>Log out</Link>
</Menu.Item>}
</Menu>
</Header>
);
}
}
export default Navigation;
This will give us a nice navigation bar. I used the Link
component from reach router to link our pages. We don't have the login page, yet. I also added a event when the user clicks on the 'Log out' button to be redirected to the login page.
So let's create a login component.
As we did before navigate inside the components folder and create a Login.js
file.
The code is similar to the registration page, except this time we will use Firebase API to sign in.
import React, { Component } from 'react';
import { Card, Col, Row, Form, Input, Button } from 'antd';
import { navigate } from '@reach/router';
import firebase from './Firebase';
class Login extends Component {
constructor() {
super();
this.state = {
email: '',
password: '',
errorMessage: null
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
const itemName = e.target.name;
const itemValue = e.target.value;
this.setState({ [itemName]: itemValue });
}
handleSubmit(e) {
const loginInfo = {
email: this.state.email,
password: this.state.password
}
// Prevent the default behaviour of the form
e.preventDefault();
// log in user with email and password
firebase.auth().signInWithEmailAndPassword(loginInfo.email, loginInfo.password)
.then(() => {
// Navigate to the root of our app - navigate is a method provided by reach router
navigate('/');
}).catch(error => {
if (error.message !== null) {
this.setState({ errorMessage: error.message })
} else {
this.setState({ errorMessage: null });
}
});
}
render() {
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 4 }
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 }
}
}
return (
<Row gutter={16}>
<Col span={12}>
<Card title="Log in" bordered={true}>
{this.state.errorMessage !== null ? (<div style={{ color: 'red' }}>{this.state.errorMessage}</div>) : null}
<Form {...formItemLayout} onSubmit={this.handleSubmit}>
<Form.Item label="Email">
<Input name="email" value={this.state.email} onChange={this.handleChange} />
</Form.Item>
<Form.Item label="Password">
<Input.Password name="password" value={this.state.password} onChange={this.handleChange} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Log in
</Button>
</Form.Item>
</Form>
</Card>
</Col>
</Row>
);
}
}
export default Login;
The most amazing thing is that the login is composed from a single line of code firebase.auth().signInWithEmailAndPassword(email, password)
🤯.
Here we also use another method from the reach router library navigate()
. In the most simple form it accepts a string. We passed a /
meaning that we want to go to the root of our app since we want to KISS😏.
Now we can go to our App.js
and start making some changes. The first thing that I'm going to do is to transform it to a class and import our registration, navigation and login components, along with firebase and some antdesign stuff.
First step? Delete all from App.js and replace it with the following:
import React, { Component } from 'react';
import { Router, navigate } from '@reach/router';
import firebase from './components/Firebase';
import { Layout } from 'antd';
import './App.css';
import Register from './components/Register';
import Navigation from './components/Navigation';
import Login from './components/Login';
const { Content } = Layout;
class App extends Component {
constructor() {
super();
this.state = {
user: null,
displayName: null,
userID: null
};
}
componentDidMount() {
// When a user is logged in, get related data
firebase.auth().onAuthStateChanged(FBUser => {
if (FBUser) {
this.setState({
user: FBUser,
displayName: FBUser.displayName,
userID: FBUser.uid
});
} else {
this.setState({ user: null });
}
});
}
/**
* Registers a user using the Firebase api
*/
registerUser = userName => {
// When something changes about authentication, this event gets generated
firebase.auth().onAuthStateChanged(FBUser => {
// Push information to firebase
FBUser.updateProfile({
displayName: userName
}).then(() => {
this.setState({
user: FBUser,
displayName: FBUser.displayName,
userID: FBUser.uid
});
navigate('/');
});
});
}
/**
* Logs out the current authenticated user
*/
logOutUser = e => {
e.preventDefault();
this.setState({
user: null,
displayName: null,
userID: null
});
firebase.auth().signOut().then(() => navigate('/login'));
}
render() {
return (
<React.Fragment>
<Layout className="layout">
<Navigation user={this.state.displayName} logOutUser={this.logOutUser} />
<Content>
<div style={{ background: '#fff', padding: 24, minHeight: 280 }}>
{this.state.displayName}
<Router>
<Login path="/login" user={this.state.user} />
<Register path="/register" registerUser={this.registerUser} />
</Router>
</div>
</Content>
</Layout>
</React.Fragment>
);
}
}
export default App;
Whoa... that is a lot. Let's break it down.
constructor
Our first look will be inside the constructor, where we will set the state object for this component with 3 properties(user, displayName and userID).
When we register a new user, Firebase will generate a User UID composed of random numbers and letters.
componentDidMount
When a user signs in our out we want to update the state of the current user. To do this we will use firebase.auth().onAuthStateChanged()
that will return a FirebaseUser object if a user is logged in.
registerUser
When we created the Register.js component, after we registered a user we called this method through props. The first part is similar to the componenentDidMount
method, except that we also update the profile of the user with the displayName and redirect the page to the root of the application. This method gets triggered when we are on the /register
page.
logOutUser
This method will sign out a user and then redirect to the /login
page. We pass this to the navigation component through props. Here we also clear the state of the current user.
render
One of the crazy things about React is that it will only return a single thing. So it forces you to enter a div
element or other type of container.
Another solution is <React.Fragment>
. This will still return a single thing, but the fragment will not be rendered on the page. So you are no longer forced to use a container for your code🙂.
Now we can npm run start
and test the registration and log in functionality, along with the routing😄.
After you register a user, you can check the Firebase console > Develop > Authentication > User tab in order to see the registered users.
Conclusion
It feels like we covered a lot in this post. In my next one I will continue this small app and integrate it with a Firebase Realtime DB. I will also explore some advanced routing and custom authorization rules🤗.
Tips:
You can install a browser extension called React Developer Tools for Firefox and Chrome in order to facilitate debugging. If you are using anything else 😥, download one of these🤣.
Top comments (1)
Ant Design UI Components
Ant Design is a popular ReactJS UI library that is used to build the design system for enterprise-level products. The Ant Design contains a rich set of high-quality components for building efficient & interactive user interfaces with an enjoyable work experience. Ant design facilitates a huge number of UI components to enrich your web applications.
Read more.