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:
Javascript
| 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";functionApp() {    return(        <div className="App">            <BrowserRouter>                <Routes>                    <Route exact path="/"                        element={<SignIn />} />                    <Route path="/Signup"                        element={<SignUp />} />                    <Route path="/chat-home/:receiverId"                        element={<ChatHome />} />                </Routes>            </BrowserRouter>        </div>    );}export defaultApp; | 
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
- If the user is not registered give an alert as the user has not found
- 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 defaultfunctionSignIn() {    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.
Javascript
| 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";functionCopyright(props) {    return(        <Typography            variant="body2"            color="text.secondary"            align="center"            {...props}        >            {"Copyright © "}                Your Website            </Link>{" "}            {newDate().getFullYear()}            {"."}        </Typography>    );}const theme = createTheme();export defaultfunctionSignUp() {    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: newDate(),            });            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->receiverId->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 && receiverData) {
        await addDoc(
          collection(
            db,
            "users",// Collection
            user.uid,// sender doc id
            "chatUsers",//Collection
            receiverData.userId,//receiver doc id
            "messages"// Collection
          ),
          {
            username: user.displayName,
            messageUserId: user.uid,
            message: chatMessage,
            timestamp: new Date(),
          }
        );
        await addDoc(
          collection(
            db,
            "users",//Collection
            receiverData.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 (receiverData) {
      const unsub = onSnapshot(
        query(
          collection(
            db,
            "users",
            user?.uid,
            "chatUsers",
            receiverData?.userId,
            "messages"
          ),
          orderBy("timestamp")
        ),
        (snapshot) => {
          setAllMessages(
            snapshot.docs.map((doc) => ({
              id: doc.id,
              messages: doc.data(),
            }))
          );
        }
      );
      return unsub;
    }
  }, [receiverData?.userId]);
ChatHome.js: Below is the code for the “ChatHome.js” file.
Javascript
| 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";functionUsersComponent(props) {    const handleToggle = (username, userId) => {        props.setReceiverData({            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 defaultfunctionHome() {    const [users, setUsers] = useState([]);    const [receiverData, setReceiverData] = 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()));        });        returnunsub;    }, []);    useEffect(() => {        if(receiverData) {            const unsub = onSnapshot(                query(                    collection(                        db,                        "users",                        user?.uid,                        "chatUsers",                        receiverData?.userId,                        "messages"                    ),                    orderBy("timestamp")                ),                (snapshot) => {                    setAllMessages(                        snapshot.docs.map((doc) => ({                            id: doc.id,                            messages: doc.data(),                        }))                    );                }            );            returnunsub;        }    }, [receiverData?.userId]);    const sendMessage = async () => {        try{            if(user && receiverData) {                await addDoc(                    collection(                        db,                        "users",                        user.uid,                        "chatUsers",                        receiverData.userId,                        "messages"                    ),                    {                        username: user.displayName,                        messageUserId: user.uid,                        message: chatMessage,                        timestamp: newDate(),                    }                );                await addDoc(                    collection(                        db,                        "users",                        receiverData.userId,                        "chatUsers",                        user.uid,                        "messages"                    ),                    {                        username: user.displayName,                        messageUserId: user.uid,                        message: chatMessage,                        timestamp: newDate(),                    }                );            }        } 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}                        setReceiverData={setReceiverData}                        navigate={navigate}                        currentUserId={user?.uid}                    />                </div>            </Paper>            <Paper style={right}>                <h4 style={{ margin: 2, padding: 10 }}>                    {receiverData ? receiverData.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
Javascript
| 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


 
                                    







