coderain guide

Building a Real-Time Chat Application with React

Real-time communication has become a cornerstone of modern web applications, powering everything from social media platforms to collaborative tools. Among the most common real-time features is chat functionality, which allows users to exchange messages instantly. In this tutorial, we’ll build a fully functional real-time chat application using **React** for the frontend and **Socket.io** for real-time communication. React’s component-based architecture makes it ideal for building dynamic UIs, while Socket.io simplifies bi-directional, low-latency communication between clients and servers. By the end of this guide, you’ll have a chat app where users can join with a username, send messages, see others typing, and view message timestamps—all in real-time.

Table of Contents

  1. Prerequisites
  2. Project Setup
  3. Choosing a Real-Time Backend
  4. Building the React Frontend
  5. Implementing Real-Time Communication
  6. Adding Advanced Features
  7. Testing the Application
  8. Deployment
  9. Conclusion
  10. References

Prerequisites

Before starting, ensure you have the following tools installed:

  • Node.js (v14+ recommended) and npm (or yarn)
  • Basic knowledge of React (components, hooks like useState and useEffect)
  • Familiarity with JavaScript (ES6+) and HTML/CSS
  • A code editor (e.g., VS Code)

Project Setup

We’ll start by creating a new React project and setting up a basic Node.js server for real-time communication.

Step 1: Create the React Frontend

Open your terminal and run:

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

Step 2: Install Dependencies

We’ll need socket.io-client (to connect to the Socket.io server) and react-router-dom (for navigation between login and chat screens). Install them with:

npm install socket.io-client react-router-dom

Step 3: Set Up the Node.js Backend

Create a separate folder for the server (or use a subfolder in the project):

mkdir server
cd server
npm init -y
npm install express socket.io cors

Choosing a Real-Time Backend

For real-time communication, we’ll use Socket.io, a library that enables low-latency, bidirectional communication between clients and servers. It works by establishing a WebSocket connection (with fallbacks like HTTP long-polling) and allows the server to broadcast messages to all connected clients instantly.

Alternatives like Firebase Realtime Database or Pusher exist, but Socket.io is lightweight, self-hosted, and ideal for learning the fundamentals of real-time communication.

Building the React Frontend

Setting Up the Project Structure

Organize your React project into folders for components, pages, and utilities:

src/
├── components/       # Reusable UI components (Message, MessageList, etc.)
├── pages/            # Full pages (Login, ChatRoom)
├── utils/            # Helper functions (e.g., format timestamps)
├── App.js            # Main app with routing
└── index.js          # Entry point

Creating Core Components

1. Login Page (pages/Login.js)

Users will enter a username before joining the chat. Create src/pages/Login.js:

import { useState } from 'react';
import { useNavigate } from 'react-router-dom';

const Login = () => {
  const [username, setUsername] = useState('');
  const navigate = useNavigate();

  const handleSubmit = (e) => {
    e.preventDefault();
    if (username.trim()) {
      navigate('/chat', { state: { username } }); // Pass username to chat room
    }
  };

  return (
    <div className="login-container">
      <h1>Join Chat Room</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Enter your username"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
          required
        />
        <button type="submit">Join Chat</button>
      </form>
    </div>
  );
};

export default Login;

2. Chat Room Page (pages/ChatRoom.js)

This page will display messages, a message input, and user info. Create src/pages/ChatRoom.js:

import { useState, useEffect, useRef } from 'react';
import { useLocation } from 'react-router-dom';
import io from 'socket.io-client';
import MessageList from '../components/MessageList';
import MessageInput from '../components/MessageInput';

const socket = io('http://localhost:3001'); // Connect to Socket.io server

const ChatRoom = () => {
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState('');
  const location = useLocation();
  const { username } = location.state; // Get username from login
  const messagesEndRef = useRef(null); // For auto-scrolling to new messages

  // Load previous messages (optional: fetch from a database)
  useEffect(() => {
    // Listen for incoming messages from the server
    socket.on('chat message', (message) => {
      setMessages((prev) => [...prev, message]);
    });

    return () => socket.disconnect(); // Cleanup on unmount
  }, []);

  // Auto-scroll to bottom when new messages arrive
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);

  const sendMessage = (e) => {
    e.preventDefault();
    if (input.trim()) {
      const message = {
        username,
        text: input,
        timestamp: new Date().toISOString(),
      };
      socket.emit('chat message', message); // Send message to server
      setInput(''); // Clear input
    }
  };

  return (
    <div className="chat-container">
      <h1>Chat Room</h1>
      <p>Welcome, {username}!</p>
      <MessageList messages={messages} currentUser={username} />
      <div ref={messagesEndRef} /> {/* Scroll target */}
      <MessageInput input={input} setInput={setInput} onSubmit={sendMessage} />
    </div>
  );
};

export default ChatRoom;

3. MessageList Component (components/MessageList.js)

Displays a list of messages. Create src/components/MessageList.js:

import Message from './Message';

const MessageList = ({ messages, currentUser }) => {
  return (
    <div className="message-list">
      {messages.map((msg, index) => (
        <Message key={index} message={msg} isCurrentUser={msg.username === currentUser} />
      ))}
    </div>
  );
};

export default MessageList;

4. Message Component (components/Message.js)

Renders a single message with the username, text, and timestamp. Create src/components/Message.js:

import { formatTimestamp } from '../utils/format';

const Message = ({ message, isCurrentUser }) => {
  return (
    <div className={`message ${isCurrentUser ? 'current-user' : ''}`}>
      <div className="message-header">
        <span className="username">{message.username}</span>
        <span className="timestamp">{formatTimestamp(message.timestamp)}</span>
      </div>
      <div className="message-text">{message.text}</div>
    </div>
  );
};

export default Message;

5. MessageInput Component (components/MessageInput.js)

Handles message input and submission. Create src/components/MessageInput.js:

const MessageInput = ({ input, setInput, onSubmit }) => {
  return (
    <form onSubmit={onSubmit} className="message-input">
      <input
        type="text"
        placeholder="Type a message..."
        value={input}
        onChange={(e) => setInput(e.target.value)}
        required
      />
      <button type="submit">Send</button>
    </form>
  );
};

export default MessageInput;

6. Format Timestamp Utility (utils/format.js)

Create src/utils/format.js to format timestamps:

export const formatTimestamp = (isoString) => {
  const date = new Date(isoString);
  return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
};

Setting Up Routing in App.js

Update src/App.js to use react-router-dom for navigation between login and chat:

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Login from './pages/Login';
import ChatRoom from './pages/ChatRoom';
import './App.css';

function App() {
  return (
    <Router>
      <div className="App">
        <Routes>
          <Route path="/" element={<Login />} />
          <Route path="/chat" element={<ChatRoom />} />
        </Routes>
      </div>
    </Router>
  );
}

export default App;

Implementing Real-Time Communication

Setting Up the Socket.io Server

In the server folder, create index.js (the Socket.io server):

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const cors = require('cors');

const app = express();
app.use(cors());

const server = http.createServer(app);
const io = new Server(server, {
  cors: {
    origin: "http://localhost:3000", // Allow React frontend
    methods: ["GET", "POST"]
  }
});

// Track connected users (optional)
const connectedUsers = new Set();

io.on('connection', (socket) => {
  console.log('A user connected:', socket.id);

  // Listen for "chat message" events from clients
  socket.on('chat message', (message) => {
    io.emit('chat message', message); // Broadcast to all clients
  });

  // Handle disconnection
  socket.on('disconnect', () => {
    console.log('User disconnected:', socket.id);
  });
});

const PORT = 3001;
server.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Connecting React to the Socket.io Server

In ChatRoom.js, we already imported socket.io-client and connected to the server with:

import io from 'socket.io-client';
const socket = io('http://localhost:3001');

The server listens for chat message events and broadcasts them to all clients. The React app emits chat message events when the user sends a message and listens for incoming messages to update the messages state.

Adding Advanced Features

User Authentication (Username Input)

We already implemented a basic login flow with react-router-dom. For production, you might add password validation or integrate OAuth (e.g., Firebase Auth, Auth0), but a simple username input works for this tutorial.

Message Timestamps and User Avatars

Timestamps

We already use formatTimestamp to display message times. Enhance it to show dates for older messages:

// utils/format.js
export const formatTimestamp = (isoString) => {
  const date = new Date(isoString);
  const now = new Date();
  const isToday = date.toDateString() === now.toDateString();

  if (isToday) {
    return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  }
  return date.toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
};

Avatars

Add simple avatars using the username’s initials. Update Message.js:

<div className="message-avatar">{message.username.charAt(0).toUpperCase()}</div>

Typing Indicators

Add “User is typing…” functionality by emitting “typing” events when the user types:

Update ChatRoom.js:

const [typingUser, setTypingUser] = useState(null);

// Emit "typing" event when input changes
useEffect(() => {
  if (input.trim()) {
    socket.emit('typing', username);
  } else {
    socket.emit('stop typing');
  }

  // Listen for "typing" events from server
  socket.on('typing', (user) => {
    setTypingUser(user);
    // Clear typing indicator after 3 seconds
    const timeout = setTimeout(() => setTypingUser(null), 3000);
    return () => clearTimeout(timeout);
  });

  socket.on('stop typing', () => setTypingUser(null));
}, [input, username, socket]);

Update the Server (server/index.js)

Add handlers for “typing” and “stop typing” events:

socket.on('typing', (username) => {
  socket.broadcast.emit('typing', username); // Broadcast to others
});

socket.on('stop typing', () => {
  socket.broadcast.emit('stop typing');
});

Display Typing Indicator in ChatRoom.js:

{typingUser && <div className="typing-indicator">{typingUser} is typing...</div>}

Testing the Application

  1. Start the Socket.io server:

    cd server
    node index.js
  2. Start the React app in a new terminal:

    cd react-realtime-chat
    npm start
  3. Open http://localhost:3000 in multiple browsers/tabs. Enter a username, send messages, and verify they appear in real-time across tabs. Test typing indicators by typing in one tab and watching another.

Deployment

Deploy the Socket.io Server

Host the Node.js server on platforms like Heroku, Render, or Railway:

  • For Heroku:
    1. Create a Procfile in the server folder: web: node index.js
    2. Push to Heroku: git push heroku main

Deploy the React Frontend

Host the React app on Vercel or Netlify:

  1. Push your React project to GitHub.
  2. Connect to Vercel/Netlify and deploy from the repo.

Update the Socket.io connection URL in ChatRoom.js to use your deployed server (e.g., https://your-server.herokuapp.com).

Conclusion

You’ve built a real-time chat app with React and Socket.io! Key features include real-time messaging, typing indicators, and user authentication. To enhance it further, consider adding:

  • File attachments (images, documents)
  • Group chat rooms
  • Read receipts
  • Message editing/deletion

References