OpenReplay
Navigate back to the homepage
BLOG
Browse Repo
Back

Real-time Chat Application with Firebase and Material UI

Ekekenta Odionyenfe . C
September 8th, 2021 · 5 min read

The need to make our web application faster for people to communicate has made it imperative for real-time features to be implemented in our chat applications. In this tutorial, we will learn how to build a real-time chat application using React.js and material UI. We set up Firebase, record, and send voice notes, and create custom users login, great right? So let get started!

Creating the project

First, let’s create a React application using the create-react-app. Which will download and installs the necessary packages required for a React application.

1npx create-react-app realChat
2cd realChat

Now, let’s look at the folder structure our application.

null

  • /components: contains our reusable widgets.
  • App.js: the root component, which will connect our components together.

Installing packages

Now, we have created a react application for our project. let go ahead and install firebase, react-audio-player, audio-react-recorder, which are the packages we will be using for this project.

1npm install firebase react-audio-player, audio-react-recorder

Setting up Firebase

We need to create a Firebase project for our application. Firebase is a platform developed by Google for creating mobile and web applications, which helps you build and run successful apps. First, head over to the Firebase Console and create a new project by clicking add project.

null

Next, enter the name of the project. in this case, RealChat and hit the continue button.

null

After creating the project, we are redirected to the dashboard. Perhaps, we need to connect our app to Firebase, so copy the configuration details for our project by clicking on the web icon (</>) in the dashboard.

null

Next, we are prompted to register your application. Type in realchat on the text box and register.

null

Firebase generates configuration details for our project. Leave this page open for now, we will be needing these credentials later in our project.

Creating the Material UI

We will create our user’s interface using Material UI. First, we need to get it installed in our application.

1npm install @material-ui/core

We will also need some icons, Material UI has a lot of cool icons we can use. Let’s go ahead and install material-ui/icons.

1npm install @material-ui/icons

So, now that we have Material UI and material-ui/icons installed in our application. Let’s go ahead and create our user interface.

Creating Users Login Component

Let’s add some code to App.js to create our login component. We will import React and Material UI components required for our login page. Then we imported the **Rooms** component, which we will be creating later in a subsequent section.

1import React from "react";
2import Button from "@material-ui/core/Button";
3import CssBaseline from "@material-ui/core/CssBaseline";
4import TextField from "@material-ui/core/TextField";
5import Box from "@material-ui/core/Box";
6import Typography from "@material-ui/core/Typography";
7import { makeStyles } from "@material-ui/core/styles";
8import Container from "@material-ui/core/Container";
9import Rooms from "./components/Rooms/Rooms";
10import "./styles.css";

Next, we will create our login component with a username field to enable us to get the user’s names. Then we create a useStyles function to override some of the Material UI styles.

1function LoginPage() {
2
3 //states definitions
4 //methods definition
5
6 const useStyles = makeStyles((theme) => ({
7 paper: {
8 marginTop: theme.spacing(8),
9 display: "flex",
10 flexDirection: "column",
11 alignItems: "center"
12 },
13 form: {
14 width: "100%", // Fix IE 11 issue.
15 marginTop: theme.spacing(1)
16 },
17 submit: {
18 margin: theme.spacing(3, 0, 2)
19 }
20 }));
21 const classes = useStyles();
22 return (
23 <Container component="main" maxWidth="xs">
24 <CssBaseline />
25 <div className={classes.paper}>
26 <Typography component="h1" variant="h5">
27 Sign in
28 </Typography>
29 <form
30 className={classes.form}
31 onSubmit={(e) => {
32 e.preventDefault();
33 handleSubmit();
34 }}
35 >
36 <TextField
37 variant="outlined"
38 margin="normal"
39 required
40 fullWidth
41 id="name"
42 label="Name"
43 name="name"
44 autoComplete="name"
45 onChange={handleChange}
46 value={name}
47 />
48 <Button
49 type="submit"
50 fullWidth
51 variant="contained"
52 color="primary"
53 className={classes.submit}
54 type="submit"
55 >
56 Sign In
57 </Button>
58 </form>
59 </div>
60 <Box mt={8}></Box>
61 </Container>
62 );
63}

Next, we need to save the values from the input field to a state. So let’s create the name state and set the initial value to an empty string.

1const [name, setName] = React.useState("");

Now, let’s create two methods, to save the input values to our state and to handle the submit event when a user tries to log in.

1function handleSubmit() {
2 props.setUser(() => name);
3 props.setLogged((prev) => !prev);
4}
5
6function handleChange(e) {
7 const { value } = e.target;
8 setName(() => value);
9}

Next, let’s render our login component. Since we are not storing the logged-in users in the database, we will create a state in our App ****component to save the name of the logged in user and their current log status. Then create handleLog function which will reset the state, and pass it as a prop to the Login component.

Next, we need to only give users who are recognized to have access to the Room component to chat. So we check the current logged state which returns either true or false and sends them to the appropriate page.

For now, our Room component is not yet ready, we will drop a comment for now till later in the section when it will be created.

1function App() {
2 const [logged, setLogged] = React.useState(false);
3 const [user, setUser] = React.useState(null);
4
5 function handleLog(){
6 setLogged(prev => !prev)
7 setUser(()=>null)
8 }
9 return (
10 <div>
11 {logged ? (
12 //Show the chat Room component
13 ) : (
14 <LoginPage setLogged={setLogged} setUser={setUser} />
15 )}
16 </div>
17 );
18}
19module.exports = App;

Creating Chat Rooms Component

We now successfully created our Login page. Now, let’s create our chat rooms component. First, we have to import React, Firebase, the required Material UI components, and import the Chats component which we will create later. Add this code to components/Rooms/Rooms.

1import React, { Component } from "react";
2import firebase from "firebase/app";
3import "firebase/firestore";
4import AudioReactRecorder, { RecordState } from "audio-react-recorder";
5import AppBar from "@material-ui/core/AppBar";
6import Toolbar from "@material-ui/core/Toolbar";
7import Typography from "@material-ui/core/Typography";
8import Button from "@material-ui/core/Button";
9import Container from "@material-ui/core/Container";
10import TextField from "@material-ui/core/TextField";
11import MicIcon from "@material-ui/icons/Mic";
12import MicOffIcon from "@material-ui/icons/MicOff";
13import styles from "./rooms.module.css";
14import Chats from "./Chats";

Next, we create our header section with a brand and a logout button, then we create a form for sending text chats and a record icon to record and send voice notes.

We create two different icons, one to record a voice message and the other to stop and send. Then a state to know if the user is recording or done recording.

Next, we created some states for our component and set their initial values. To enable us to style them in our own way, we created a custom CSS style. Then we create a state object to store the chats, form input, recording status, Error message, and record state.

1class Rooms extends Component {
2 constructor(props) {
3 super(props);
4 this.state = {
5 activeUser: this.props.activeUser,
6 value: "",
7 isRecording: false,
8 chats: [],
9 Error: null,
10 recordState: null,
11 };
12 }
13 render() {
14 const { recordState } = this.state;
15 return (
16 <Container maxWidth="lg">
17 <AppBar position="absolute">
18 <Toolbar className={styles.navRow}>
19 <Typography component="h1" variant="h6" color="inherit" noWrap>
20 Real Chat
21 </Typography>
22 <Button
23 variant="contained"
24 color="secondary"
25 onClick={this.props.handleLogout}
26 >
27 Logout
28 </Button>
29 </Toolbar>
30 </AppBar>
31 <main className={styles.main}>
32 //chats conponents gose here!
33 </main>
34 <form
35 className={`${styles.form}`}
36 noValidate
37 autoComplete="off"
38 >
39 <div className={styles.formCol}>
40 <TextField
41 id="outlined-textarea"
42 placeholder="Say somthing.."
43 multiline
44 variant="outlined"
45 className={styles.text}
46 name="value"
47 />
48 {this.state.isRecording ? (
49 <MicOffIcon className={styles.mic} />
50 ) : (
51 <MicIcon className={styles.mic}/>
52 )}
53 <Button
54 variant="contained"
55 color="primary"
56 className={styles.textBtn}
57 type="submit"
58 >
59 Send
60 </Button>
61 </div>
62 </form>
63 <div>
64 <AudioReactRecorder
65 state={recordState}
66 canvasWidth="0"
67 canvasHeight="0"
68 />
69 );
70 </div>
71 </Container>
72 );
73 }
74}
75module.exports = Rooms;

Add these styles to component/Rooms/rooms.module.css.

1.navRow {
2 display: flex;
3 justify-content: space-between;
4}
5.main {
6 padding: 10px;
7 height: 80vh;
8 margin: 10vh 0 10vh;
9 overflow-y: scroll;
10 display: flex;
11 flex-direction: column;
12}
13.main::-webkit-scrollbar {
14 width: 0.3rem;
15}
16.main::-webkit-scrollbar-track {
17 background: #0b93f6;
18}
19.main::-webkit-scrollbar-thumb {
20 background: #3f51b5;
21}
22.text {
23 max-width: 500px;
24 margin-bottom: 12px;
25 line-height: 24px;
26 padding: 10px 7px;
27 border-radius: 10px;
28 position: relative;
29 color: white;
30 text-align: center;
31}
32.audio {
33 max-width: 500px;
34 margin-bottom: 12px;
35 line-height: 24px;
36 padding: 7px 7px;
37 border-radius: 10px;
38 position: relative;
39 color: black;
40 text-align: center;
41 background-color: #f1f1f1fd;
42}
43.message {
44 display: flex;
45}
46.sent {
47 flex-direction: row-reverse;
48}
49.sent .text {
50 color: white;
51 background: #0b93f6;
52 align-self: flex-end;
53}
54.received .text {
55 background: #e5e5ea;
56 color: black;
57}
58.form {
59 position: fixed;
60 bottom: 0;
61 display: flex;
62 align-items: center;
63 font-size: 1.5rem;
64 background-color: white;
65}
66.mic {
67 cursor: pointer;
68 font-size: 40px;
69}
70.textBtn {
71 padding: 10px;
72 margin: 6px;
73}
74.formCol {
75 display: flex;
76 align-items: center;
77}

Next, let’s create our handler functions. We’ll import and initialize Firebase using the configuration details generated earlier when registering the app in the Firebase dashboard. Then, initialize firestore, which will enable us to save our chats to Firebase in real-time. Add this code after the imports.

1const firebaseConfig = {
2 //Your firebase configuration objects
3};
4// Initialize Firebase
5if (!firebase.apps.length) {
6 firebase.initializeApp(firebaseConfig);
7}
8const db = firebase.firestore();

Creating Voice Message

Then, we will create methods for the AudioReactRecorder component to handle the start, and stop of the recording.

1start = () => {
2 this.setState({
3 recordState: RecordState.START,
4 isRecording: !this.state.isRecording
5 });
6 };
7 stop = () => {
8 this.setState({
9 recordState: RecordState.STOP,
10 isRecording: !this.state.isRecording
11 });
12 };

Now, we need to save the message to the database. To do that that we will create a function to handle the submitting of forms to the database since we have two types of messages to send.

The function accepts the user message, and the category then submits to Firestore to perform a write to the database.

Then we listen to the onstop event, which is triggered when the user hits the stop button which returns an audio record.

1async sumitData(msg, cate) {
2 await db.collection("chats").add({
3 message: msg,
4 createdAt: firebase.firestore.FieldValue.serverTimestamp(),
5 user: this.state.activeUser,
6 cate: cate
7 });
8}
9//audioData contains blob and blobUrl
10 onStop = async (audioData) => {
11 console.log(audioData);
12 try {
13 await this.sumitData(audioData, "voice");
14 } catch (error) {
15 console.log(error);
16 this.setState({ Error: error.message });
17 }
18 };
19};

Open Source Session Replay

Debugging a web application in production may be challenging and time-consuming. OpenReplay is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue. It’s like having your browser’s inspector open while looking over your user’s shoulder. OpenReplay is the only open-source alternative currently available.

OpenReplay

Happy debugging, for modern frontend teams - Start monitoring your web app for free.

Creating Text Message

Next, let’s now create handler functions to enable the users to send text messages.

We create a handleChange method to save the user’s message to our state. Then the handleSubmit method, which will get the message stored in our state when the users entered their details, and save to the Firebase by calling the sumitData method.

1handleChange(e) {
2 this.setState({ value: e.target.value });
3 }
4
5async handleSubmit(e) {
6 e.preventDefault();
7 const { value } = this.state;
8 try {
9 await this.sumitData(value, "text");
10 this.setState({ value: "" });
11 } catch (error) {
12 console.log(error);
13 this.setState({ Error: error.message });
14 }
15 }
16
17 componentDidMount() {
18 if (db) {
19 db.collection("chats")
20 .orderBy("createdAt")
21 .onSnapshot((querySnapShot) => {
22 const chats = querySnapShot.docs.map((docs) => ({
23 ...docs.data(),
24 id: docs.id
25 }));
26 this.setState({ chats });
27 });
28 }
29 }

Next, we create a hook to fetch our message from Firebase. This data is fetched when our components load and reload when a user sends a new message without the user refreshing the browser.

We then loop through the messages using a map function. Then pass chats and the active user as props to the Chats components

1<main className={styles.main}>
2 {this.state.chats.map((el) => (
3 <Chats
4 styles={styles}
5 chat={el}
6 key={el.id}
7 activeUser={this.props.activeUser}
8 />
9 ))}
10</main>

Next, let’s bind the handleChange and handleSubmit to our class and, then chain the methods to their respective component to listen to events.

1this.handleChange = this.handleChange.bind(this);
2this.handleSubmit = this.handleSubmit.bind(this);

Lastly, we need to render our Room component in our App component. Modify App.js component, replace the //Show the chat Room component with this code.

1<Rooms activeUser={user} handleLog={handleLog}/>

Creating Chat Component

Next, we need to create a component to display the user’s chats. We will import React, ReactAudioPlayer, and the required Material UI components, then import our custom styles.

Then, we destruct the props to get the message, user and the category of the message fetch from Firebase. Since the message sent by the user has a special style to other users, we will check if the messages are sent by active users and apply the styles.

Next, we check if the chat category is a voice message or a text, to either load the voice note to the audio player or display the messages.

Add these codes to component/Rooms/Charts

1import React, { Component } from "react";
2import ReactAudioPlayer from "react-audio-player";
3import styles from "./rooms.module.css";
4class Chats extends Component {
5 render() {
6 const {
7 chat: { cate, user, message }
8 } = this.props;
9 const activeUser = this.props.activeUser;
10 return (
11 <>
12 {this.props.chat.cate === "text" ? (
13 <div
14 className={`${styles.message} ${
15 user === activeUser ? styles.sent : styles.received
16 }`}
17 >
18 <div className={styles.text}>
19 <h4 style={{ textAlign: "left" }}>{user}</h4>
20 <span>{message}</span>
21 </div>
22 </div>
23 ) : (
24 <div
25 className={`${styles.message} ${
26 user === activeUser ? styles.sent : styles.received
27 }`}
28 >
29 <div className={styles.audio}>
30 <h4 style={{ textAlign: "left" }}>{user}</h4>
31 <ReactAudioPlayer controls src={message}/>
32 </div>
33 </div>
34 )}
35 </>
36 );
37 }
38}
39module.exports = Chats;

Resources

Github Code

Conclusion

Throughout this tutorial, you’ve learned how to build a React real-time chat application. You’ve also experienced setting up Firebase, creating a user interface with Material, and Sending text messages as well as voice notes. Now, how would you implement real-time features in your next project? Perhaps creating your chat system with more features?

More articles from OpenReplay Blog

Virtualizing Large Data Lists with react-window

Rendering large dataset in the DOM without the right tools can be expensive, learn how to use react-window to simply this task

September 2nd, 2021 · 5 min read

Building Your Own Blog with Nuxt Content and Tailwind

Ever thought about building your own blog? Learn how to easily do that with Nuxt and TailwindCSS.

August 30th, 2021 · 11 min read
© 2021 OpenReplay Blog
Link to $https://twitter.com/OpenReplayHQLink to $https://github.com/openreplay/openreplayLink to $https://www.linkedin.com/company/18257552