In this React JS tutorial, we will show you how to build React Firebase chat web application (React Hooks). This application using Firebase realtime database, so, it not necessary to use additional real-time engines such as Socket.io.
This tutorial divided into several steps:
- Step #1: Setup Firebase Real-time Database
- Step #2: Create React JS App
- Step #3: Install and Configure the Required Modules
- Step #4: Add React Router DOM
- Step #5: Implementing Login
- Step #6: Implementing Room List and Add Room
- Step #7: Implementing Chat Room
- Step #8: Run and Test React JS Firebase Chat Web App
The flow is very simple as described in this diagram.
- The first entry of this React JS application is the login page.
- In the login page, it will check if the nickname exists in the local storage.
- If the nickname exists, it will go to the room list directly, and if not exists, it will stay on the login page to enter the nickname in the login form.
- The request from the login form will compare with the user's document in the Firebase Realtime-Database.
- If the nickname exists in the Firebase document then it will go to the room list and set the nickname to the local storage.
- If the nickname does not exist in the Firebase then it will save the nickname first to the users' document before going to the room list and set to the local storage.
- The room list contains a list of the chat room, a current user nickname, add the room button, and a logout button.
- The add button action will go to the add room form then compare the submitted room name to the Firebase realtime-database document.
- If the room name exists, it will return an alert, and if not exists, it will save a new room name to Firebase document.
- The new room will automatically appear on the room list.
- The logout button action will log out and exit the room list by removing the nickname from the local storage and redirect the page to the login page.
- Each room item in the room list has an action to go to the chat room.
- In the chat room, there is a chat box that contains the message, a message form, the online user's lists, and an exit button that has the action to go back to the room list.
The following tools, frameworks, libraries, and modules are required for this tutorial:
- Node.js and NPM or Yarn
- create-react-app CLI
- Reactstrap
- Firebase Module
- Firebase Real-time Database
- Terminal (Mac/Linux) or Node Command Line (Windows)
- IDE or Text Editor (We are using Visual Studio Code)
Let get started to the main steps!
You can watch the video tutorial from our YouTube channel here. Please like, share, comments, and subscribe to our Channel.
Step #1: Setup Firebase Real-time Database
We will set up or create a new Google Firebase project that can use the realtime database. Just open your browser then go to Google Firebase Console and you will take to this page.
From that page, click the Create Project button to create a Google Firebase project then it will be redirected to this page.
After filling the project name text field which our project name is "ReactChat" and select your parent resources to your domain (ours: "djamware.com") then click the continue button and it will be redirected to this page.
This time, choose to not add Firebase analytics for now then click Create Project button. Now, you have a Google Firebase Project ready to use.
After clicking the Continue button it will be redirected to this page.
Choose the Database menu in the left pane then click the Create Database button.
Select "Start in test mode" then click next and it will go to the next dialog.
Select the Firebase database server location (better near your React JS server location) then click the Done button. Don't forget to select or change Cloud Firestore to Realtime Database in Develop -> Database dashboard. Next, go to the Rules tab and you will see these rules values.
{
/* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
"rules": {
".read": false,
".write": false
}
}
Change it to readable and writeable from everywhere for this tutorial only.
{
/* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
"rules": {
".read": "auth === null",
".write": "auth === null"
}
}
Click the publish button to update or save the changes. Now, the Google Firebase realtime database is ready to use with your React JS web app.
Step #2: Create React JS App
To create a new React JS application, we will use the create-react-app tool. The create-react-app is a tool to create a React JS app from the command line or CLI. So you don’t need to install or configure tools like Webpack or Babel because they are preconfigured and hidden so that you can focus on the code. Type this command to install it.
sudo npm install -g create-react-app
Now, we can create a new React.js app using that CLI tool.
create-react-app react-chat
This command will create a new React app with the name `react-chat` and this process can take minutes because all dependencies and modules also installing automatically. Next, go to the newly created app folder.
cd ./react-chat
Open the project in your IDE or text editor and see the content of package.json.
"dependencies": {
...
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-scripts": "3.4.0"
},
That React version is the version that already uses React Hooks as default. Now, `src/App.js` doesn't use class anymore. For sanitation, run this React app for the first time by type this command.
yarn start
And here' we go the latest React.js application that uses React Hooks with the same initial home page.
Step #3: Install and Configure the Required Modules
We use the Firebase module to access the Firebase Firestore Database and Reactstrap to use Bootstrap within React JS. The Firebase module is available as an NPM module. Also, we need an additional library for scrolling to the bottom of the chatbox react-scroll-to-bottom and formatting date using Moment.js. For that, type these commands to install the module.
yarn add firebase reactstrap bootstrap react react-dom react-scroll-to-bottom react-router-dom moment
Next, create a new file `Firebase.js` in the root of the project folder for Firebase configuration.
touch src/Firebase.js
Open and edit `src/Firebase.js` then replace all codes with this.
import * as firebase from 'firebase';
import firestore from 'firebase/firestore'
const settings = {timestampsInSnapshots: true};
const config = {
projectId: 'YOUR_FIREBASE_PROJECT_ID',
apiKey: 'YOUR_API_KEY',
databaseURL: 'YOUR_DATABASE_URL'
};
firebase.initializeApp(config);
firebase.firestore().settings(settings);
export default firebase;
You can find ProjectID and API Key in the Firebase console under Settings(gear icon) and there is Web API Key. For databaseURL, go to the Service Accounts tab under Settings, and you will see databaseURL in Admin SDK configuration snippet for Node.js. There are no configurations for other libraries or modules, just, use them in the React JS component later.
Step #4: Add React Router DOM
As we mention in the application flow, there are few pages required. Login, Room List, Add Room, and Chat Room pages. For that add those components first by type these commands.
mkdir src/components
touch src/components/Login.js
touch src/components/RoomList.js
touch src/components/AddRoom.js
touch src/components/ChatRoom.js
Next, open and edit `src/index.js` then add/replace these imports.
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter } from "react-router-dom";
Next, replace the React.DOM render with this.
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
Next, open and edit `src/App.js` then replace all imports with these.
import React from 'react';
import './App.css';
import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
useLocation
} from "react-router-dom";
import Login from './components/Login';
import RoomList from './components/RoomList';
import AddRoom from './components/AddRoom';
import ChatRoom from './components/ChatRoom';
Modify the App function to add the React Router that mapping to those imported React components with default landing page redirect to RoomList component.
function App() {
let location = useLocation();
return (
<Router>
<div>
<Redirect
to={{
pathname: "/roomlist",
state: { from: location }
}}
/>
<Switch>
<Route path="/login">
<Login />
</Route>
<SecureRoute path="/roomlist">
<RoomList />
</SecureRoute>
<SecureRoute path="/addroom">
<AddRoom />
</SecureRoute>
<SecureRoute path="/chatroom/:room">
<ChatRoom />
</SecureRoute>
</Switch>
</div>
</Router>
);
}
export default App;
As you see in the above React Router, there are <SecureRoute> tag which means, the route for those components must pass the filter for security. Add this function of SecureRoute to filter the logged in users only that can enter those components. Otherwise, it will redirect to the login page.
function SecureRoute({ children, ...rest }) {
return (
<Route
{...rest}
render={({ location }) =>
localStorage.getItem('nickname') ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
);
}
Step #5: Implementing Login
The login component will contain the form with an input text and a submit button. The data from that form will be submitted to the Firebase Realtime-Database by check the existing nickname first then save to Firebase if there's no match nickname found. Open and edit `src/components/Login.js` then add these imports.
import React, { useState } from 'react';
import { useHistory } from "react-router-dom";
import {
Jumbotron,
Spinner,
Form,
Button,
FormGroup,
Label,
Input
} from 'reactstrap';
import firebase from '../Firebase';
Add a main function after the imports.
function Login() {
}
export default Login;
Declare the variables for the required form field, loading spinner trigger, history, and Firebase Database reference.
const history = useHistory();
const [creds, setCreds] = useState({ nickname: '' });
const [showLoading, setShowLoading] = useState(false);
const ref = firebase.database().ref('users/');
Add a function to handle the input value changes.
const onChange = (e) => {
e.persist();
setCreds({...creds, [e.target.name]: e.target.value});
}
Add a function to submit the data to the Firebase realtime-database.
const login = (e) => {
e.preventDefault();
setShowLoading(true);
ref.orderByChild('nickname').equalTo(creds.nickname).once('value', snapshot => {
if (snapshot.exists()) {
localStorage.setItem('nickname', creds.nickname);
history.push('/roomlist');
setShowLoading(false);
} else {
const newUser = firebase.database().ref('users/').push();
newUser.set(creds);
localStorage.setItem('nickname', creds.nickname);
history.push('/roomlist');
setShowLoading(false);
}
});
};
Next, render the view of the login page by adding this return that contains HTML tags with React components.
return (
<div>
{showLoading &&
<Spinner color="primary" />
}
<Jumbotron>
<Form onSubmit={login}>
<FormGroup>
<Label>Nickname</Label>
<Input type="text" name="nickname" id="nickname" placeholder="Enter Your Nickname" value={creds.nickname} onChange={onChange} />
</FormGroup>
<Button variant="primary" type="submit">
Login
</Button>
</Form>
</Jumbotron>
</div>
);
Step #6: Implementing Room List and Add Room
Now, we will implement a Room List that contains a list of the chat rooms, add room button, logout button, and logged in user information. Open and edit `src/components/RoomList.js` then add these required imports.
import React, { useState, useEffect } from 'react';
import {
Link,
useHistory
} from "react-router-dom";
import {
Jumbotron,
Spinner,
ListGroup,
ListGroupItem,
Button
} from 'reactstrap';
import Moment from 'moment';
import firebase from '../Firebase';
Add a main function after the imports.
function RoomList() {
}
export default RoomList;
Inside that main function, declare all required variables.
const [room, setRoom] = useState([]);
const [showLoading, setShowLoading] = useState(true);
const [nickname, setNickname] = useState('');
const history = useHistory();
Load the room list from the Firebase realtime-database inside the useEffect function.
useEffect(() => {
const fetchData = async () => {
setNickname(localStorage.getItem('nickname'));
firebase.database().ref('rooms/').on('value', resp => {
setRoom([]);
setRoom(snapshotToArray(resp));
setShowLoading(false);
});
};
fetchData();
}, []);
In that function, the response from the Firebase needs to extract to the array of objects by using snapshotToArray function. For that, add that function.
const snapshotToArray = (snapshot) => {
const returnArr = [];
snapshot.forEach((childSnapshot) => {
const item = childSnapshot.val();
item.key = childSnapshot.key;
returnArr.push(item);
});
return returnArr;
}
Add a function to enter the chat room while sending the data that contain enter room status to the Firebase Database.
const enterChatRoom = (roomname) => {
const chat = { roomname: '', nickname: '', message: '', date: '', type: '' };
chat.roomname = roomname;
chat.nickname = nickname;
chat.date = Moment(new Date()).format('DD/MM/YYYY HH:mm:ss');
chat.message = `${nickname} enter the room`;
chat.type = 'join';
const newMessage = firebase.database().ref('chats/').push();
newMessage.set(chat);
firebase.database().ref('roomusers/').orderByChild('roomname').equalTo(roomname).on('value', (resp) => {
let roomuser = [];
roomuser = snapshotToArray(resp);
const user = roomuser.find(x => x.nickname === nickname);
if (user !== undefined) {
const userRef = firebase.database().ref('roomusers/' + user.key);
userRef.update({status: 'online'});
} else {
const newroomuser = { roomname: '', nickname: '', status: '' };
newroomuser.roomname = roomname;
newroomuser.nickname = nickname;
newroomuser.status = 'online';
const newRoomUser = firebase.database().ref('roomusers/').push();
newRoomUser.set(newroomuser);
}
});
history.push('/chatroom/' + roomname);
}
Add a function to logout from the chat application.
const logout = () => {
localStorage.removeItem('nickname');
history.push('/login');
}
Add a view for this component.
return (
<div>
{showLoading &&
<Spinner color="primary" />
}
<Jumbotron>
<h3>{nickname} <Button onClick={() => { logout() }}>Logout</Button></h3>
<h2>Room List</h2>
<div>
<Link to="/addroom">Add Room</Link>
</div>
<ListGroup>
{room.map((item, idx) => (
<ListGroupItem key={idx} action onClick={() => { enterChatRoom(item.roomname) }}>{item.roomname}</ListGroupItem>
))}
</ListGroup>
</Jumbotron>
</div>
);
Next, to add a new room, open and edit `src/components/AddRoom.js` then add these imports.
import React, { useState } from 'react';
import {
useHistory
} from "react-router-dom";
import {
Alert,
Jumbotron,
Spinner,
Form,
Button,
FormGroup,
Label,
Input
} from 'reactstrap';
import firebase from '../Firebase';
Add a main function for this component.
function AddRoom() {
}
export default AddRoom;
Declare the required variable inside the main function body.
const history = useHistory();
const [room, setRoom] = useState({ roomname: '' });
const [showLoading, setShowLoading] = useState(false);
const ref = firebase.database().ref('rooms/');
Add a function to save the room data to the Firebase realtime-database.
const save = (e) => {
e.preventDefault();
setShowLoading(true);
ref.orderByChild('roomname').equalTo(room.roomname).once('value', snapshot => {
if (snapshot.exists()) {
return (
<div>
<Alert color="primary">
Room name already exist!
</Alert>
</div>
);
} else {
const newRoom = firebase.database().ref('rooms/').push();
newRoom.set(room);
history.goBack();
setShowLoading(false);
}
});
};
Add a function to handle the input value changes.
const onChange = (e) => {
e.persist();
setRoom({...room, [e.target.name]: e.target.value});
}
Add the view for this add room form.
return (
<div>
{showLoading &&
<Spinner color="primary" />
}
<Jumbotron>
<h2>Please enter new Room</h2>
<Form onSubmit={save}>
<FormGroup>
<Label>Room Name</Label>
<Input type="text" name="roomname" id="roomname" placeholder="Enter Room Name" value={room.roomname} onChange={onChange} />
</FormGroup>
<Button variant="primary" type="submit">
Add
</Button>
</Form>
</Jumbotron>
</div>
);
Step #7: Implementing Chat Room
The chat room implementation will contain chat-box, a list of online users, and message form. Open and edit `src/components/ChatRoom.js` then add these required imports.
import React, { useState, useEffect } from 'react';
import {
useHistory,
useParams
} from "react-router-dom";
import {
Container,
Row,
Col,
Card,
CardBody,
CardSubtitle,
Button,
Form,
InputGroup,
Input,
InputGroupAddon
} from 'reactstrap';
import Moment from 'moment';
import firebase from '../Firebase';
import ScrollToBottom from 'react-scroll-to-bottom';
import '../Styles.css';
Add a main function after the imports.
function ChatRoom(props) {
}
export default ChatRoom;
Add the required variables inside the main function body.
const [chats, setChats] = useState([]);
const [users, setUsers] = useState([]);
const [nickname, setNickname] = useState('');
const [roomname, setRoomname] = useState('');
const [newchat, setNewchat] = useState({ roomname: '', nickname: '', message: '', date: '', type: '' });
const history = useHistory();
const { room } = useParams();
Add the useEffect function that loads the chats data from the Firebase realtime-database.
useEffect(() => {
const fetchData = async () => {
setNickname(localStorage.getItem('nickname'));
setRoomname(room);
firebase.database().ref('chats/').orderByChild('roomname').equalTo(roomname).on('value', resp => {
setChats([]);
setChats(snapshotToArray(resp));
});
};
fetchData();
}, [room, roomname]);
Add again the useEffect function that loads the user's data from the Firebase realtime-database.
useEffect(() => {
const fetchData = async () => {
setNickname(localStorage.getItem('nickname'));
setRoomname(room);
firebase.database().ref('roomusers/').orderByChild('roomname').equalTo(roomname).on('value', (resp2) => {
setUsers([]);
const roomusers = snapshotToArray(resp2);
setUsers(roomusers.filter(x => x.status === 'online'));
});
};
fetchData();
}, [room, roomname]);
Add a function that extracts the Firebase response to the array of objects.
const snapshotToArray = (snapshot) => {
const returnArr = [];
snapshot.forEach((childSnapshot) => {
const item = childSnapshot.val();
item.key = childSnapshot.key;
returnArr.push(item);
});
return returnArr;
}
Add a function that submits a new message from the message form to the Firebase database.
const submitMessage = (e) => {
e.preventDefault();
const chat = newchat;
chat.roomname = roomname;
chat.nickname = nickname;
chat.date = Moment(new Date()).format('DD/MM/YYYY HH:mm:ss');
chat.type = 'message';
const newMessage = firebase.database().ref('chats/').push();
newMessage.set(chat);
setNewchat({ roomname: '', nickname: '', message: '', date: '', type: '' });
};
Add a function that handles the input changes.
const onChange = (e) => {
e.persist();
setNewchat({...newchat, [e.target.name]: e.target.value});
}
Add a function to exit the chat room while sending status data to the Firebase.
const exitChat = (e) => {
const chat = { roomname: '', nickname: '', message: '', date: '', type: '' };
chat.roomname = roomname;
chat.nickname = nickname;
chat.date = Moment(new Date()).format('DD/MM/YYYY HH:mm:ss');
chat.message = `${nickname} leave the room`;
chat.type = 'exit';
const newMessage = firebase.database().ref('chats/').push();
newMessage.set(chat);
firebase.database().ref('roomusers/').orderByChild('roomname').equalTo(roomname).once('value', (resp) => {
let roomuser = [];
roomuser = snapshotToArray(resp);
const user = roomuser.find(x => x.nickname === nickname);
if (user !== undefined) {
const userRef = firebase.database().ref('roomusers/' + user.key);
userRef.update({status: 'offline'});
}
});
history.goBack();
}
Add the view for this chat room.
return (
<div className="Container">
<Container>
<Row>
<Col xs="4">
<div>
<Card className="UsersCard">
<CardBody>
<CardSubtitle>
<Button variant="primary" type="button" onClick={() => { exitChat() }}>
Exit Chat
</Button>
</CardSubtitle>
</CardBody>
</Card>
{users.map((item, idx) => (
<Card key={idx} className="UsersCard">
<CardBody>
<CardSubtitle>{item.nickname}</CardSubtitle>
</CardBody>
</Card>
))}
</div>
</Col>
<Col xs="8">
<ScrollToBottom className="ChatContent">
{chats.map((item, idx) => (
<div key={idx} className="MessageBox">
{item.type ==='join'||item.type === 'exit'?
<div className="ChatStatus">
<span className="ChatDate">{item.date}</span>
<span className="ChatContentCenter">{item.message}</span>
</div>:
<div className="ChatMessage">
<div className={`${item.nickname === nickname? "RightBubble":"LeftBubble"}`}>
{item.nickname === nickname ?
<span className="MsgName">Me</span>:<span className="MsgName">{item.nickname}</span>
}
<span className="MsgDate"> at {item.date}</span>
<p>{item.message}</p>
</div>
</div>
}
</div>
))}
</ScrollToBottom>
<footer className="StickyFooter">
<Form className="MessageForm" onSubmit={submitMessage}>
<InputGroup>
<Input type="text" name="message" id="message" placeholder="Enter message here" value={newchat.message} onChange={onChange} />
<InputGroupAddon addonType="append">
<Button variant="primary" type="submit">Send</Button>
</InputGroupAddon>
</InputGroup>
</Form>
</footer>
</Col>
</Row>
</Container>
</div>
);
On that chat room view, we use a lot of CSS classes to build a proper chatbox. For that, add a CSS file `src/Styles.css` then add these lines of CSS codes.
.Container {
display: flex;
padding: 10px;
}
.UsersCard {
margin: 5px 20px;
}
footer.StickyFooter {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
padding: 10px;
background-color: #ffffff;
border-top: solid 1px #efefef;
}
.MessageForm {
margin-left: 10px;
}
.MessageBox {
float: left;
width: 98%;
margin: 5px 0 0 2%;
}
.MessageBox .ChatMessage {
width: 80%;
min-height: 40px;
}
.MessageBox .ChatMessage .RightBubble {
position: relative;
background: #dcf8c6;
border-top-left-radius: .4em;
border-bottom-left-radius: .4em;
border-bottom-right-radius: .4em;
padding: 5px 10px 10px;
left: 15%;
}
.MessageBox .ChatMessage .RightBubble span.MsgName {
font-size: 12px;
font-weight: bold;
color: green;
}
.MessageBox .ChatMessage .RightBubble span.MsgDate {
font-size: 10px;
}
.MessageBox .ChatMessage .RightBubble:after {
content: '';
position: absolute;
right: 0;
top: 13px;
width: 0;
height: 0;
border: 27px solid transparent;
border-left-color: #dcf8c6;
border-right: 0;
border-top: 0;
margin-top: -13.5px;
margin-right: -27px;
}
.MessageBox .ChatMessage .LeftBubble {
position: relative;
background: lightblue;
border-top-right-radius: .4em;
border-bottom-left-radius: .4em;
border-bottom-right-radius: .4em;
padding: 5px 10px 10px;
left: 5%;
}
.MessageBox .ChatMessage .LeftBubble span.MsgName {
font-size: 12px;
font-weight: bold;
color: blue;
}
.MessageBox .ChatMessage .LeftBubble span.MsgDate {
font-size: 10px;
}
.MessageBox .ChatMessage .LeftBubble:after {
content: '';
position: absolute;
left: 0;
top: 13px;
width: 0;
height: 0;
border: 27px solid transparent;
border-right-color: lightblue;
border-left: 0;
border-top: 0;
margin-top: -13.5px;
margin-left: -27px;
}
.MessageBox .ChatStatus {
min-height: 49px;
}
.MessageBox .ChatStatus .ChatDate {
display: block;
font-size: 10px;
font-style: italic;
color: #777;
height: 15px;
left: 10%;
right:10%;
}
.MessageBox .ChatStatus .ChatContentCenter {
padding: 5px 10px;
background-color: #e1e1f7;
border-radius: 6px;
font-size: 12px;
color: #555;
height: 34px;
left: 10%;
right:10%;
}
.ChatContent {
overflow-y: scroll;
height: 600px;
}
This CSS file already imported in the ChatRoom.js.
Step #8: Run and Test React JS Firebase Chat Web App
To run this React JS Firebase Chat Web App, type this command in the terminal or CMD at the root of this project folder.
yarn start
Open the browser then go to the `localhost:3000`. You can test this chat activity by opening different browsers or different computers and then go to the same address. And here the React JS chat web app looks like.
That it's, the React JS Tutorial: Building Firebase Chat App (React Hooks). You can get the full source code from our GitHub.
That just the basic. If you need more deep learning about MERN Stack, React.js or React Native you can take the following cheap course:
- Mastering React JS
- Master React Native Animations
- React: React Native Mobile Development: 3-in-1
- MERN Stack Front To Back: Full Stack React, Redux & Node. js
- Learning React Native Development
Thanks!