Skip to content
Related Articles

Related Articles

User to User private Chat App using ReactJS and Firebase | Without socket programming

View Discussion
Improve Article
Save Article
  • Last Updated : 31 Aug, 2022
View Discussion
Improve Article
Save Article

This article covers how to make a User to User private  Chat App using React JS and Firebase (without socket programming). React is a JavaScript framework provided by Facebook and is used to build fully-featured web applications.

We will be following the below steps for creating our application:

npx create-react-app chat-app
cd chat-app

Now install all required modules for the project by using the below command:

npm install @emotion/react @emotion/styled @mui/icons-material @mui/lab @mui/material firebase react-router-dom

Step 1: Open the “src” folder and select the App.js file. 

This file contains routing logic, where routing helps the user to navigate different pages. Go through react-router-dom documentation to understand more about routing in React JS

App.js: Below is the code for the App.js file:

App.js




import "./App.css";
import { BrowserRouter, Routes, Route } 
    from "react-router-dom";
import SignIn from "./Screens/Signin";
import SignUp from "./Screens/Signup";
import ChatHome from "./Screens/ChatHome";
   
function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <Routes>
          <Route exact path="/" 
            element={<SignIn />} />
          <Route path="/Signup" 
            element={<SignUp />} />
          <Route path="/chat-home/:recieverId"
            element={<ChatHome />} />
        </Routes>
      </BrowserRouter>
    </div>
  );
}
   
export default App;

Step 2: Create the “Screens” folder  and Create files “Signin.js”,  “Signup.js“, “ChatHome.js”

Screens folder

Step 3: Working with the Signin.js file 

Here users need to enter their email and password, Firebase will authenticate 

  1. If the user is not registered give an alert as the user has not found
  2. If the user entered the wrong credentials gives an alert as the wrong password

After Sign in success, users navigate to the Chat home page, where chatting takes place.

Signin.js: Below is the code for the Signin.js file.

Signin.js




import * as React from "react";
import Avatar from "@mui/material/Avatar";
import Button from "@mui/material/Button";
import CssBaseline from "@mui/material/CssBaseline";
import TextField from "@mui/material/TextField";
import FormControlLabel from "@mui/material/FormControlLabel";
import Checkbox from "@mui/material/Checkbox";
import Link from "@mui/material/Link";
import Grid from "@mui/material/Grid";
import Box from "@mui/material/Box";
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
import Typography from "@mui/material/Typography";
import Container from "@mui/material/Container";
import { createTheme, ThemeProvider } from "@mui/material/styles";
import { useNavigate } from "react-router-dom";
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "../Firebase";
  
const theme = createTheme();
  
export default function SignIn() {
  const [email, setEmail] = React.useState("");
  const [password, setPassword] = React.useState("");
  
  const navigate = useNavigate();
  const handleSubmit = async (event) => {
    event.preventDefault();
    signInWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        // Signed in
        const user = userCredential.user;
  
        navigate("/chat-home/1");
        // ...
      })
      .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
  
        alert(errorMessage);
      });
  };
  
  return (
    <ThemeProvider theme={theme}>
      <Container component="main" maxWidth="xs">
        <CssBaseline />
        <Box
          sx={{
            marginTop: 8,
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
          }}
        >
          <Avatar sx={{ m: 1, bgcolor: "secondary.main" }}>
            <LockOutlinedIcon />
          </Avatar>
          <Typography component="h1" variant="h5">
            Sign in
          </Typography>
          <Box
            component="form"
            onSubmit={handleSubmit}
            noValidate
            sx={{ mt: 1 }}
          >
            <TextField
              margin="normal"
              required
              fullWidth
              id="email"
              label="Email Address"
              name="email"
              autoComplete="email"
              autoFocus
              value={email}
              onChange={(e) => setEmail(e.target.value)}
            />
            <TextField
              margin="normal"
              required
              fullWidth
              name="password"
              label="Password"
              type="password"
              id="password"
              autoComplete="current-password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
            />
            <FormControlLabel
              control={<Checkbox value="remember" 
              color="primary" />}
              label="Remember me"
            />
            <Button
              type="submit"
              fullWidth
              variant="contained"
              sx={{ mt: 3, mb: 2 }}
            >
              Sign In
            </Button>
            <Grid container>
              <Grid item xs>
                <Link href="#" variant="body2">
                  Forgot password?
                </Link>
              </Grid>
              <Grid item>
                <Link href="/Signup" variant="body2">
                  {"Don't have an account? Sign Up"}
                </Link>
              </Grid>
            </Grid>
          </Box>
        </Box>
      </Container>
    </ThemeProvider>
  );
}

Output:

Signin page

Step 4: Working with the Signup.js file

Here users need to Register/Signup with a username, email, and password. After registration for every user, there will be a unique id is generated and User data is stored in Firestore.

After Registration, the user navigates to the Sign-in page.

Signup.js: Below is the code for the “Signup.js” file.

Signup.js




import * as React from "react";
import Avatar from "@mui/material/Avatar";
import Button from "@mui/material/Button";
import CssBaseline from "@mui/material/CssBaseline";
import TextField from "@mui/material/TextField";
import Link from "@mui/material/Link";
import Grid from "@mui/material/Grid";
import Box from "@mui/material/Box";
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
import Typography from "@mui/material/Typography";
import Container from "@mui/material/Container";
import { createTheme, ThemeProvider } from "@mui/material/styles";
import { auth, db } from "../Firebase";
import { createUserWithEmailAndPassword, 
    updateProfile } from "firebase/auth";
import { doc, setDoc } from "firebase/firestore";
import { useNavigate } from "react-router-dom";
function Copyright(props) {
  return (
    <Typography
      variant="body2"
      color="text.secondary"
      align="center"
      {...props}
    >
      {"Copyright © "}
      <Link color="inherit" href="https://mui.com/">
        Your Website
      </Link>{" "}
      {new Date().getFullYear()}
      {"."}
    </Typography>
  );
}
  
const theme = createTheme();
  
export default function SignUp() {
  const [username, setUsername] = React.useState("");
  const [email, setEmail] = React.useState("");
  const [password, setPassword] = React.useState("");
  
  const navigate = useNavigate();
  const handleSubmit = async (event) => {
    event.preventDefault();
  
    try {
      const userCredential = await createUserWithEmailAndPassword(
        auth,
        email,
        password
      );
      const update = await updateProfile(auth.currentUser, {
        displayName: username,
      });
  
      const user = userCredential.user;
  
      setDoc(doc(db, "users", user.uid), {
        username: username,
        email: email,
        userId: user.uid,
        timestamp: new Date(),
      });
  
      navigate("/");
    } catch (error) {
      alert(error.message);
    }
  };
  
  return (
    <ThemeProvider theme={theme}>
      <Container component="main" maxWidth="xs">
        <CssBaseline />
        <Box
          sx={{
            marginTop: 8,
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
          }}
        >
          <Avatar sx={{ m: 1, bgcolor: "secondary.main" }}>
            <LockOutlinedIcon />
          </Avatar>
          <Typography component="h1" variant="h5">
            Sign up
          </Typography>
          <Box
            component="form"
            noValidate
            onSubmit={handleSubmit}
            sx={{ mt: 3 }}
          >
            <Grid container spacing={2}>
              <Grid item xs={12}>
                <TextField
                  autoComplete="given-name"
                  name="firstName"
                  required
                  fullWidth
                  id="firstName"
                  label="First Name"
                  autoFocus
                  value={username}
                  onChange={(e) => setUsername(e.target.value)}
                />
              </Grid>
  
              <Grid item xs={12}>
                <TextField
                  required
                  fullWidth
                  id="email"
                  label="Email Address"
                  name="email"
                  autoComplete="email"
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                />
              </Grid>
              <Grid item xs={12}>
                <TextField
                  required
                  fullWidth
                  name="password"
                  label="Password"
                  type="password"
                  id="password"
                  autoComplete="new-password"
                  value={password}
                  onChange={(e) => setPassword(e.target.value)}
                />
              </Grid>
            </Grid>
            <Button
              type="submit"
              fullWidth
              variant="contained"
              sx={{ mt: 3, mb: 2 }}
            >
              Sign Up
            </Button>
            <Grid container justifyContent="flex-end">
              <Grid item>
                <Link href="/" variant="body2">
                  Already have an account? Sign in
                </Link>
              </Grid>
            </Grid>
          </Box>
        </Box>
        <Copyright sx={{ mt: 5 }} />
      </Container>
    </ThemeProvider>
  );
}

signup page

Step 5: Working with the ChatHome.js file

Here users find all registered users on the left side, by clicking on any target user navigate to the private chat room with the target user, and start sending messages.

Now where actual chatting logic comes, whenever the user clicks on send message button, the below sendMessage function triggers and 

creates a firestorm reference as users->userid->chatUsers->recieverId->messages for sender user and receiver user.

A Reference represents a specific location in your Database and can be used for reading or writing data to that Database location

const sendMessage = async () => {
    try {
      if (user && recieverData) {
        await addDoc(
          collection(
            db,
            "users",// Collection
            user.uid,// sender doc id
            "chatUsers",//Collection
            recieverData.userId,//reciever doc id
            "messages"// Collection
          ),
          {
            username: user.displayName,
            messageUserId: user.uid,
            message: chatMessage,
            timestamp: new Date(),
          }
        );

        await addDoc(
          collection(
            db,
            "users",//Collection
            recieverData.userId,// receiver doc id
            "chatUsers",//Collection
            user.uid,//sender doc id
            "messages"//Collection
          ),
          {
            username: user.displayName,
            messageUserId: user.uid,
            message: chatMessage,
            timestamp: new Date(),
          }
        );
      }
    } catch (error) {
      console.log(error);
    }
    setChatMessage("");
  };

below useEffect React hook is for reading all messages for the “messages” collection in Firebase, to know more about how react hooks works go through React hooks documentation

  useEffect(() => {
    if (recieverData) {
      const unsub = onSnapshot(
        query(
          collection(
            db,
            "users",
            user?.uid,
            "chatUsers",
            recieverData?.userId,
            "messages"
          ),
          orderBy("timestamp")
        ),
        (snapshot) => {
          setAllMessages(
            snapshot.docs.map((doc) => ({
              id: doc.id,
              messages: doc.data(),
            }))
          );
        }
      );
      return unsub;
    }
  }, [recieverData?.userId]);

ChatHome.js: Below is the code for the “ChatHome.js” file.

ChatHome.js




import React, { useEffect, useState } from "react";
import Paper from "@mui/material/Paper";
import { Button, Divider, IconButton } from "@mui/material";
import SendIcon from "@mui/icons-material/Send";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import ListItemAvatar from "@mui/material/ListItemAvatar";
import Avatar from "@mui/material/Avatar";
import { useNavigate } from "react-router";
  
import { db, auth } from "../Firebase";
import {
  addDoc,
  collection,
  onSnapshot,
  orderBy,
  query,
} from "firebase/firestore";
  
function UsersComponent(props) {
  const handleToggle = (username, userId) => {
    props.setRecieverData({
      username: username,
      userId: userId,
    });
  
    props.navigate(`/chat-home/${userId}`);
  };
  
  return (
    <List
      dense
      sx={{ width: "100%", maxWidth: 360, 
              bgcolor: "background.paper" }}
    >
      {props.users?.map((value, index) => {
        const labelId = `checkbox-list-secondary-label-${value}`;
  
        if (props.currentUserId !== value.userId)
          return (
            <ListItem key={value.userId} disablePadding>
              <ListItemButton
                onClick={() => {
                  handleToggle(value.username, value.userId);
                }}
              >
                <ListItemAvatar>
                  <Avatar
                    alt={`${value.username}`}
                    src={`${value.username}.jpg`}
                  />
                </ListItemAvatar>
                <ListItemText id={labelId} 
                        primary={`${value.username}`} />
              </ListItemButton>
            </ListItem>
          );
      })}
    </List>
  );
}
  
export default function Home() {
  const [users, setUsers] = useState([]);
  
  const [recieverData, setRecieverData] = useState(null);
  const [chatMessage, setChatMessage] = useState("");
  
  const [allMessages, setAllMessages] = useState([]);
  
  const user = auth.currentUser;
  
  const navigate = useNavigate();
  
  useEffect(() => {
    const unsub = onSnapshot(collection(db, "users"), (snapshot) => {
      setUsers(snapshot.docs.map((doc) => doc.data()));
    });
    return unsub;
  }, []);
  
  useEffect(() => {
    if (recieverData) {
      const unsub = onSnapshot(
        query(
          collection(
            db,
            "users",
            user?.uid,
            "chatUsers",
            recieverData?.userId,
            "messages"
          ),
          orderBy("timestamp")
        ),
        (snapshot) => {
          setAllMessages(
            snapshot.docs.map((doc) => ({
              id: doc.id,
              messages: doc.data(),
            }))
          );
        }
      );
      return unsub;
    }
  }, [recieverData?.userId]);
  
  const sendMessage = async () => {
    try {
      if (user && recieverData) {
        await addDoc(
          collection(
            db,
            "users",
            user.uid,
            "chatUsers",
            recieverData.userId,
            "messages"
          ),
          {
            username: user.displayName,
            messageUserId: user.uid,
            message: chatMessage,
            timestamp: new Date(),
          }
        );
  
        await addDoc(
          collection(
            db,
            "users",
            recieverData.userId,
            "chatUsers",
            user.uid,
            "messages"
          ),
          {
            username: user.displayName,
            messageUserId: user.uid,
            message: chatMessage,
            timestamp: new Date(),
          }
        );
      }
    } catch (error) {
      console.log(error);
    }
    setChatMessage("");
  };
  
  return (
    <div style={root}>
      <Paper style={left}>
        <div
          style={{
            display: "flex",
            padding: 5,
            justifyContent: "space-between",
          }}
        >
          <h4 style={{ margin: 0 }}>{user?.displayName} </h4>
          <Button
            color="secondary"
            onClick={() => {
              auth.signOut();
              navigate("/");
            }}
          >
            Logout
          </Button>
        </div>
        <Divider />
        All users
        <div style={{ overflowY: "scroll" }}>
          <UsersComponent
            users={users}
            setRecieverData={setRecieverData}
            navigate={navigate}
            currentUserId={user?.uid}
          />
        </div>
      </Paper>
  
      <Paper style={right}>
        <h4 style={{ margin: 2, padding: 10 }}>
          {recieverData ? recieverData.username : user?.displayName}{" "}
        </h4>
  
        <Divider />
        <div style={messagesDiv}>
          {/* messages area */}
  
          {allMessages &&
            allMessages.map(({ id, messages }) => {
              return (
                <div
                  key={id}
                  style={{
                    margin: 2,
                    display: "flex",
                    flexDirection:
                      user?.uid == messages.messageUserId
                        ? "row-reverse"
                        : "row",
                  }}
                >
                  <span
                    style={{
                      backgroundColor: "#BB8FCE",
                      padding: 6,
                      borderTopLeftRadius:
                        user?.uid == messages.messageUserId ? 10 : 0,
                      borderTopRightRadius:
                        user?.uid == messages.messageUserId ? 0 : 10,
                      borderBottomLeftRadius: 10,
                      borderBottomRightRadius: 10,
                      maxWidth: 400,
                      fontSize: 15,
                      textAlign:
                        user?.uid == messages.messageUserId ? "right" : "left",
                    }}
                  >
                    {messages.message}
                  </span>
                </div>
              );
            })}
        </div>
  
        <div style={{ width: "100%", display: "flex", flex: 0.08 }}>
          <input
            value={chatMessage}
            onChange={(e) => setChatMessage(e.target.value)}
            style={input}
            type="text"
            placeholder="Type message..."
          />
          <IconButton onClick={sendMessage}>
            <SendIcon style={{ margin: 10 }} />
          </IconButton>
        </div>
      </Paper>
    </div>
  );
}
  
const root = {
  display: "flex",
  flexDirection: "row",
  flex: 1,
  width: "100%",
};
  
const left = {
  display: "flex",
  flex: 0.2,
  height: "95vh",
  margin: 10,
  flexDirection: "column",
};
  
const right = {
  display: "flex",
  flex: 0.8,
  height: "95vh",
  margin: 10,
  flexDirection: "column",
};
  
const input = {
  flex: 1,
  outline: "none",
  borderRadius: 5,
  border: "none",
};
  
const messagesDiv = {
  backgroundColor: "#FBEEE6",
  padding: 5,
  display: "flex",
  flexDirection: "column",
  flex: 1,
  maxHeight: 460,
  overflowY: "scroll",
};

Step 6: Open Firebase  go to console -> Create a new project  -> copy the firebaseConfig. Now Create “Firebase.js” file in src folder

Step 7: working with the Firebase.js file

Here we are going to integrate Firebase with React JS, Go through Firebase Documentation to find the Integration logic. 

Now paste the firebaseConfig in the below-commented place

Firebase.js: The below code is for the “Firebase.js” file

Firebase.js




import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
  
    // paste Copied firebaseConfig here 
      
};
  
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const auth = getAuth(app);
export { db, auth };

Step 8: Running and Building the application: We can run this application by using the following command. This will start React’s development server that can be used for debugging our application.

npm run start

Output:

Private chatting between two users


My Personal Notes arrow_drop_up
Recommended Articles
Page :

Start Your Coding Journey Now!