Chatting App with ReactJS and Firebase(Realtime database)
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