Real-time Chat Application with Firebase and Material UI
Tags: react, tutorial, firebase
Published: 2021-09-08 09:46:58

_by author [Ekekenta Odionyenfe . C](https://blog.openreplay.com/authors/ekekenta-odionyenfe-c)_

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](https://github.com/facebook/create-react-app). Which will download and installs the necessary packages required for a React application.


```
npx create-react-app realChat
cd realChat
```


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

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m16od58ym7y70hm97gx9.png)

- `/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](https://www.npmjs.com/package/firebase), [react-audio-player](https://www.npmjs.com/package/react-audio-player), [audio-react-recorder](https://www.npmjs.com/package/audio-react-recorder), which are the packages we will be using for this project.


```
npm 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](https://console.firebase.google.com/) and create a new project by clicking **add project.**


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pt1cdq3lsrz00cwae8i6.png)


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


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/00oiiakqrcuoczbi5078.png)


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.


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h7dz9fn10bnzl36x880t.png)


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


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ygpwewpmslxeuccjb8cc.png)


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](https://material-ui.com/). First, we need to get it installed in our application.


```
npm 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](https://www.npmjs.com/package/@material-ui/icons).


```
npm 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.


```javascript
import React from "react";
import Button from "@material-ui/core/Button";
import CssBaseline from "@material-ui/core/CssBaseline";
import TextField from "@material-ui/core/TextField";
import Box from "@material-ui/core/Box";
import Typography from "@material-ui/core/Typography";
import { makeStyles } from "@material-ui/core/styles";
import Container from "@material-ui/core/Container";
import Rooms from "./components/Rooms/Rooms";
import "./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.


```javascript
function LoginPage() {

 //states definitions
 //methods definition

 const useStyles = makeStyles((theme) => ({
  paper: {
   marginTop: theme.spacing(8),
   display: "flex",
   flexDirection: "column",
   alignItems: "center"
  },
  form: {
   width: "100%", // Fix IE 11 issue.
   marginTop: theme.spacing(1)
  },
  submit: {
   margin: theme.spacing(3, 0, 2)
  }
 }));
 const classes = useStyles();
 return (
  <Container component="main" maxWidth="xs">
   <CssBaseline />
   <div className={classes.paper}>
    <Typography component="h1" variant="h5">
     Sign in
    </Typography>
    <form
     className={classes.form}
     onSubmit={(e) => {
      e.preventDefault();
      handleSubmit();
     }}
    >
     <TextField
      variant="outlined"
      margin="normal"
      required
      fullWidth
      id="name"
      label="Name"
      name="name"
      autoComplete="name"
      onChange={handleChange}
      value={name}
     />
     <Button
      type="submit"
      fullWidth
      variant="contained"
      color="primary"
      className={classes.submit}
      type="submit"
     >
      Sign In
     </Button>
    </form>
   </div>
   <Box mt={8}></Box>
  </Container>
 );
}
```


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.


```javascript
const [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.


```javascript
function handleSubmit() {
  props.setUser(() => name);
  props.setLogged((prev) => !prev);
}
 
function handleChange(e) {
  const { value } = e.target;
  setName(() => value);
}
```


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.


```javascript
function App() {
 const [logged, setLogged] = React.useState(false);
 const [user, setUser] = React.useState(null);

 function handleLog(){
   setLogged(prev => !prev)
   setUser(()=>null)
 }
 return (
  <div>
   {logged ? (
    //Show the chat Room component
   ) : (
    <LoginPage setLogged={setLogged} setUser={setUser} />
   )}
  </div>
 );
}
module.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.**


```javascript
import React, { Component } from "react";
import firebase from "firebase/app";
import "firebase/firestore";
import AudioReactRecorder, { RecordState } from "audio-react-recorder";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
import Container from "@material-ui/core/Container";
import TextField from "@material-ui/core/TextField";
import MicIcon from "@material-ui/icons/Mic";
import MicOffIcon from "@material-ui/icons/MicOff";
import styles from "./rooms.module.css";
import 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.


```javascript
class Rooms extends Component {
 constructor(props) {
  super(props);
  this.state = {
      activeUser: this.props.activeUser,
      value: "",
      isRecording: false,
      chats: [],
      Error: null,
      recordState: null,
  };
 }
 render() {
  const { recordState } = this.state;
  return (
   <Container maxWidth="lg">
    <AppBar position="absolute">
     <Toolbar className={styles.navRow}>
      <Typography component="h1" variant="h6" color="inherit" noWrap>
       Real Chat
      </Typography>
      <Button
       variant="contained"
       color="secondary"
       onClick={this.props.handleLogout}
      >
       Logout
      </Button>
     </Toolbar>
    </AppBar>
    <main className={styles.main}>
       //chats conponents gose here!
    </main>
    <form
     className={`${styles.form}`}
     noValidate
     autoComplete="off"
    >
     <div className={styles.formCol}>
      <TextField
       id="outlined-textarea"
       placeholder="Say somthing.."
       multiline
       variant="outlined"
       className={styles.text}
       name="value"
      />
      {this.state.isRecording ? (
       <MicOffIcon className={styles.mic} />
      ) : (
       <MicIcon className={styles.mic}/>
      )}
      <Button
       variant="contained"
       color="primary"
       className={styles.textBtn}
       type="submit"
      >
       Send
      </Button>
     </div>
    </form>
    <div>
     <AudioReactRecorder
      state={recordState}
      canvasWidth="0"
      canvasHeight="0"
     />
     );
    </div>
   </Container>
  );
 }
}
module.exports = Rooms;
```


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


```css
.navRow {
 display: flex;
 justify-content: space-between;
}
.main {
 padding: 10px;
 height: 80vh;
 margin: 10vh 0 10vh;
 overflow-y: scroll;
 display: flex;
 flex-direction: column;
}
.main::-webkit-scrollbar {
 width: 0.3rem;
}
.main::-webkit-scrollbar-track {
 background: #0b93f6;
}
.main::-webkit-scrollbar-thumb {
 background: #3f51b5;
}
.text {
 max-width: 500px;
 margin-bottom: 12px;
 line-height: 24px;
 padding: 10px 7px;
 border-radius: 10px;
 position: relative;
 color: white;
 text-align: center;
}
.audio {
 max-width: 500px;
 margin-bottom: 12px;
 line-height: 24px;
 padding: 7px 7px;
 border-radius: 10px;
 position: relative;
 color: black;
 text-align: center;
 background-color: #f1f1f1fd;
}
.message {
 display: flex;
}
.sent {
 flex-direction: row-reverse;
}
.sent .text {
 color: white;
 background: #0b93f6;
 align-self: flex-end;
}
.received .text {
 background: #e5e5ea;
 color: black;
}
.form {
 position: fixed;
 bottom: 0;
 display: flex;
 align-items: center;
 font-size: 1.5rem;
 background-color: white;
}
.mic {
 cursor: pointer;
 font-size: 40px;
}
.textBtn {
 padding: 10px;
 margin: 6px;
}
.formCol {
 display: flex;
 align-items: center;
}
```


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.


```javascript
const firebaseConfig = {
 //Your firebase configuration objects
};
// Initialize Firebase
if (!firebase.apps.length) {
 firebase.initializeApp(firebaseConfig);
}
const db = firebase.firestore();
```



## Creating Voice Message

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


```javascript
start = () => {
  this.setState({
   recordState: RecordState.START,
   isRecording: !this.state.isRecording
  });
 };
 stop = () => {
  this.setState({
   recordState: RecordState.STOP,
   isRecording: !this.state.isRecording
  });
 };
```


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.


```javascript
async sumitData(msg, cate) {
  await db.collection("chats").add({
   message: msg,
   createdAt: firebase.firestore.FieldValue.serverTimestamp(),
   user: this.state.activeUser,
   cate: cate
  });
}
//audioData contains blob and blobUrl
 onStop = async (audioData) => {
  console.log(audioData);
  try {
   await this.sumitData(audioData, "voice");
  } catch (error) {
   console.log(error);
   this.setState({ Error: error.message });
  }
 };
};

```
## Open Source Session Replay
Debugging a web application in production may be challenging and time-consuming. [OpenReplay](https://github.com/openreplay/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](https://raw.githubusercontent.com/openreplay/openreplay/main/static/replayer.png)

Happy debugging, for modern frontend teams - [Start monitoring your web app for free](https://github.com/openreplay/openreplay).

## 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.


```javascript
handleChange(e) {
    this.setState({ value: e.target.value });
 }

async handleSubmit(e) {
  e.preventDefault();
  const { value } = this.state;
  try {
   await this.sumitData(value, "text");
   this.setState({ value: "" });
  } catch (error) {
   console.log(error);
   this.setState({ Error: error.message });
  }
 }

 componentDidMount() {
  if (db) {
   db.collection("chats")
    .orderBy("createdAt")
    .onSnapshot((querySnapShot) => {
     const chats = querySnapShot.docs.map((docs) => ({
      ...docs.data(),
      id: docs.id
     }));
     this.setState({ chats });
    });
  }
 }
```


 

Next, we create a [hook](https://reactjs.org/docs/hooks-intro.html) 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) function. Then pass chats and the active user as [props](https://reactjs.org/docs/components-and-props.html) to the **Chats** components


```javascript
<main className={styles.main}>
 {this.state.chats.map((el) => (
    <Chats
       styles={styles}
       chat={el}
       key={el.id}
       activeUser={this.props.activeUser}
      />
    ))}
</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.


```javascript
this.handleChange = this.handleChange.bind(this);
this.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.


```html
<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**


```javascript
import React, { Component } from "react";
import ReactAudioPlayer from "react-audio-player";
import styles from "./rooms.module.css";
class Chats extends Component {
 render() {
  const {
   chat: { cate, user, message }
  } = this.props;
  const activeUser = this.props.activeUser;
  return (
  <>
    {this.props.chat.cate === "text" ? (
     <div
      className={`${styles.message} ${
       user === activeUser ? styles.sent : styles.received
      }`}
     >
      <div className={styles.text}>
       <h4 style={{ textAlign: "left" }}>{user}</h4>
       <span>{message}</span>
      </div>
     </div>
    ) : (
     <div
      className={`${styles.message} ${
       user === activeUser ? styles.sent : styles.received
      }`}
     >
      <div className={styles.audio}>
       <h4 style={{ textAlign: "left" }}>{user}</h4>
       <ReactAudioPlayer controls src={message}/>
      </div>
     </div>
    )}
   </>
  );
 }
}
module.exports = Chats;
```



## Resources

[Github Code](https://github.com/icode247/RealChat)

## 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?