Open In App

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

Improve
Improve
Like Article
Like
Save
Share
Report

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";
 
function App() {
    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 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. 

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";
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->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";
 
function UsersComponent(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 default function Home() {
    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()));
        });
        return unsub;
    }, []);
 
    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]);
 
    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: new Date(),
                    }
                );
 
                await addDoc(
                    collection(
                        db,
                        "users",
                        receiverData.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}
                        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



Last Updated : 21 Jun, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads