Chatting App with ReactJS and Firebase(Realtime database)

Rana Khurram
6 min readDec 2, 2018

--

In this article, we are going to learn that how to build a chat app using ReactJS and Firebase. If you are a front-end or full stack developer you must have heard about ReactJS. It is a JavaSscript library created by Facebook and used to create UI components.

For beginners, let’s have a brief introduction of ReactJS and Firebase.

ReactJS:

React is a JavaScript library created and used by Facebook. This library is used for building user interfaces. It is view layer of a web application. React applications are made up of components. A component acts as a UI module and renders some html output with full fledged client side functionality e.g components of buttons, tables and wizards. A component can includes one or more components in its output. After creating small components we merge them to a high level component which define the application.

Firebase:

Firebase is Backend as a Service(BAAS). BAAS is a cloud computing service model that serves as the middleware that provides developers with ways to connect their Web and mobile applications to cloud services via API and SDK. Firebase is developed by Google and some of provided services are following:

Real time database

Push notification

Firebase Analytic

Firebase Authentication

Firebase Cloud Messaging

Firebase Storage

Firebase Hosting

In our chat app we’ll use Real time database and Firebase Hosting.

Real time database is a NoSQL cloud database. Data is stored in JSON form and synchronized in realtime to every client. Every time data changes all connected clients receives updated data in milliseconds. You can access it with browser, no application server is needed for this purpose. Security and data validations are available through security rules.

Creating React App:

We’ll use create-react-app to create a react app, provided by Facebook. You don’t need to install Webpack and Babel they are pre-configured with it.

npx create-react-app chat-app

You can also use any boilerplate to start with. Now install Firebase node module.

npm install firebase --save

Give a try and run the app, a standard create-react-app page should be open in your browser.

cd chat-app
npm start

Your directory structure will be like this:

chat-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── serviceWorker.js

Now we’ll add two main components User and Chat. Add two JavaScript files User.js and Chat.js in src/ folder.

User.js

Following is the code for whole User component

import React, { Component } from 'react';
import { database } from './firebase';

export default class User extends Component {
constructor() {
super();

this.state = {
users: [],
username: ''
};
}

componentWillMount() {
const userRef = database.ref('users')
.orderByKey()
.limitToLast(100);

userRef.once('value', snapshot => {
const users = [snapshot.val()];
this.setState({users: users});
});
}

onNameChange(e) {
this.setState({username: e.target.value})
}

onAddClick(e) {
e.preventDefault();
database.ref('users').push({username: this.state.username});
localStorage.setItem('chat_username', this.state.username);
this.props.history.push('/chat');
}

render() {
return(
<div className="form-group col-md-4">
<label >Username: </label>
<input className="form-control input-sm" type="text" onChange={this.onNameChange.bind(this)}/>
<button className="btn btn-info" onClick={this.onAddClick.bind(this)}>Add</button>
</div>
);
}
}
import React, { Component } from 'react';
import { database } from './firebase';

export default class User extends Component {
constructor() {
super();

this.state = {
users: [],
username: ''
};
}

componentWillMount() {
const userRef = database.ref('users')
.orderByKey()
.limitToLast(100);

userRef.once('value', snapshot => {
const users = [snapshot.val()];
this.setState({users: users});
});
}

onNameChange(e) {
this.setState({username: e.target.value})
}

onAddClick(e) {
e.preventDefault();
database.ref('users').push({username: this.state.username});
localStorage.setItem('chat_username', this.state.username);
this.props.history.push('/chat');
}

render() {
return(
<div className="form-group col-md-4">
<label >Username: </label>
<input className="form-control input-sm" id="inputsm" type="text" onChange={this.onNameChange.bind(this)}/>
<button className="btn btn-info" onClick={this.onAddClick.bind(this)}>Add</button>
</div>
);
}
}

Creating reference to `user` model in database and retrieving users.

const userRef = database.ref('users')
.orderByKey()
.limitToLast(100);

userRef.once('value', snapshot => {
const users = [snapshot.val()];
this.setState({users: users});
});

Chat.js

This component renders all messages. Each message consist of two attributes.

Message Text

Username

import React, { Component } from 'react';
import { database } from './firebase';

export default class Chat extends Component {
constructor() {
super();

this.state = {
messages: [],
username: ''
};

this.onAddMessage = this.onAddMessage.bind(this);
}

componentWillMount() {
const username = localStorage.getItem('chat_username');
this.setState({username: username ? username : 'Unknown'})
const messagesRef = database.ref('messages')
.orderByKey()
.limitToLast(100);

messagesRef.on('value', snapshot => {
let messagesObj = snapshot.val();
let messages = [];
Object.keys(messagesObj).forEach(key => messages.push(messagesObj[key]));
messages = messages.map((message) => { return {text: message.text, user: message.user, id: message.key}})
this.setState(prevState => ({
messages: messages,
}));
});
}

onAddMessage(event) {
event.preventDefault();
database.ref('messages').push({text: this.input.value, user: this.state.username});
this.input.value = '';
}

render() {
return (
<div>
<div className="padding-13 messages-div">
<h2>Chat Messages</h2>
{this.state.messages.map((message) => {
const _class = message.user === this.state.username ? 'message-left container' : 'message-right container';
return (
<div className={_class}>
<h6 className="name-heading">{message.user}</h6>
<p className="marg-left-10">{message.text}</p>
<span className="time-left"></span>
</div>
)
})}
</div>
<div className="container textarea-div">
<textarea className="text-area" ref={node => this.input = node}></textarea>
<button className="btn btn-info send-btn " onClick={this.onAddMessage}>Send</button>
</div>
</div>
);
}
}

Following code is creating reference to chat and retrieving updated data.

const messagesRef = database.ref('messages')
.orderByKey()
.limitToLast(100);
messagesRef.on('value', snapshot => {
let messagesObj = snapshot.val();
let messages = [];
Object.keys(messagesObj).forEach(key => messages.push(messagesObj[key]));
messages = messages.map((message) => { return {text: message.text, user: message.user, id: message.key}})
this.setState(prevState => ({
messages: messages,
}));
});

Every time the data updates in database, registered callback function gets called with updated data and updates state of component.

App.js

import React, { Component } from 'react';

class App extends Component {
render() {
return (
<div>
{this.props.children}
</div>
);
}
}

export default App;

Index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import User from './User';
import Chat from './Chat';
import registerServiceWorker from './registerServiceWorker';
import { HashRouter, Route } from 'react-router-dom'

ReactDOM.render(
<HashRouter>
<div>
<Route exact path="/" component={App}></Route>
<Route exact path="/" component={User}></Route>
<Route exact path="/chat" name="chat" component={Chat}></Route></div>
</HashRouter>, document.getElementById('root'));
registerServiceWorker();

Some Styling:

Add Bootstrap link in public/index.html for responsiveness.

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">

Index.css

body {
margin: 0 auto;
max-width: 800px;
padding: 0 20px;
}

.container {
border: 2px solid #dedede;
background-color: #f1f1f1;
border-radius: 5px;
padding: 10px;
margin: 10px 0;
}

.darker {
border-color: #ccc;
background-color: #ddd;
}

.container::after {
content: "";
clear: both;
display: table;
}

.container img {
float: left;
max-width: 60px;
width: 100%;
margin-right: 20px;
border-radius: 50%;
}

.container img.right {
float: right;
margin-left: 20px;
margin-right:0;
}

.time-right {
float: right;
color: #aaa;
}

.time-left {
float: left;
color: #999;
}
img {
width: 100%;
}

.message-left {
width: 55%;
display: inline-block;
}
.message-right {
width: 55%;
float: right;
}
.text-area {
width: 90%;
height: 85px;
}
.send-btn {
margin-left: 5px;
margin-top: -60px;
}
.textarea-div {
overflow: hidden;
}
.padding-13 {
padding: 13px;
}
.messages-div {
height: 500px;
overflow: auto;
}
.name-heading {
color: cadetblue;
font-size: 20px;
font-weight: 600;
}
.marg-left-10 {
margin-left: 10px;
}

Run the app, by npm start . As application is working so let’s deploy the app on Firebase.

Deploy on Firebase:

Login to firebase, get API keys and Project ID

firebase login

Copy API key and other settings to Firebase.js like this

import * as firebase from 'firebase';

const config = {
apiKey: 'you-api-key',
authDomain: 'your-domain.firebaseapp.com',
databaseURL: 'https://<project-id>.firebaseio.com',
storageBucket: '<project-id>.appspot.com'
};

firebase.initializeApp(config);

const database = firebase.database();

export {
database,
};

Initialize Firebase app, select appropriate options for database and hosting by

firebase init

To deploy app run

firebase deploy

After running above command a url of hosted app will be given on console. Open it and Enjoy!

This is how you add realtime updates to your application using Firebase. There are several approaches using Signal R, Socket.io but Firebase provides this with much convenience.

You can find the code repository here. If you run into any issue please do not hesitate to contact me on rkhurram343@gmail.com

--

--