DEV Community

Cover image for Frontend Coding Challenge Chat-App
Bishoy Bishai
Bishoy Bishai

Posted on • Edited on • Originally published at bishoy-bishai.github.io

Frontend Coding Challenge Chat-App

Building a chat app seems easy: an input, a button, and a list. But once you have hundreds of messages, things get slow. The app might lag, scrolling becomes "janky," and your computer’s memory starts to cry.

To build something as smooth as WhatsApp or Discord, you need to understand how React handles thousands of items without breaking a sweat.


Why Chat Apps Get Slow

Most chat apps fail because of these common issues:

  1. Too many re-renders: Every time you type a letter, the whole message list refreshes.
  2. DOM Bloat: Trying to force the browser to draw 5,000 messages at once.
  3. Scroll Jumps: New messages arriving while you're trying to read old ones, causing the screen to bounce.

Strategy 1: The "Don't Repeat Yourself" Render

Our first line of defense is React.memo. This tells React: "If the message text hasn't changed, don't waste time redrawing it."

// components/Message.tsx
import React from 'react';

// Wrapping the component in React.memo is a huge performance win!
const Message = React.memo(({ sender, content, timestamp, isMine }) => {
  const messageClass = isMine ? 'bg-blue-500 text-white' : 'bg-gray-200 text-gray-800';

  return (
    <div className={`p-2 my-1 rounded-lg ${messageClass}`}>
      {!isMine && <span className="font-bold text-xs">{sender}: </span>}
      <p className="text-sm">{content}</p>
      <span className="block text-xs text-right opacity-50">{timestamp}</span>
    </div>
  );
});

export default Message;

Enter fullscreen mode Exit fullscreen mode

Strategy 2: Smooth Scrolling with useLayoutEffect

There is a tiny difference between useEffect and useLayoutEffect.

  • useEffect happens after the screen updates (can cause a visible flicker).
  • useLayoutEffect happens before the screen paints (perfect for fixing scroll positions silently).

The Chat Container Logic

// components/ChatContainer.tsx
import React, { useRef, useLayoutEffect, useState, useCallback } from 'react';
import Message from './Message';

const ChatContainer = () => {
  const [messages, setMessages] = useState([]);
  const chatScrollRef = useRef(null);
  const [isScrolledUp, setIsScrolledUp] = useState(false);

  // Keep the chat at the bottom, but only if the user isn't reading old messages
  useLayoutEffect(() => {
    const container = chatScrollRef.current;
    if (container && !isScrolledUp) {
      container.scrollTop = container.scrollHeight;
    }
  }, [messages, isScrolledUp]);

  const handleScroll = () => {
    const { scrollTop, scrollHeight, clientHeight } = chatScrollRef.current;
    // Check if user has scrolled up away from the bottom
    const isAtBottom = scrollHeight - scrollTop - clientHeight < 100;
    setIsScrolledUp(!isAtBottom);
  };

  return (
    <div className="flex flex-col h-screen p-4">
      <div 
        ref={chatScrollRef} 
        onScroll={handleScroll}
        className="flex-1 overflow-y-auto flex flex-col"
      >
        {messages.map((msg) => (
          <Message key={msg.id} {...msg} />
        ))}
      </div>

      {isScrolledUp && (
        <button className="fixed bottom-20 right-8 bg-blue-600 text-white p-2 rounded">
          New Messages Below
        </button>
      )}
    </div>
  );
};

Enter fullscreen mode Exit fullscreen mode

Pro-Tips for Success

1. The "Key" Rule

Never use the array index (0, 1, 2...) as a key prop. If a message is deleted or moved, React will get confused and slow down. Always use a unique ID from your database.

2. Don't Render Everything

If you have 10,000 messages, don't put them all in the DOM. Use Virtualization (libraries like react-window). This technique only renders the 10 or 20 messages that are actually visible on the screen.

3. Smart Updates

When adding a new message, don't "mutate" your state. Use the spread operator [...messages, newMessage] or a library like Immer. This helps React realize exactly what changed instantly.


Summary

A professional chat UI isn't just about looking good; it's about being "expensive-feeling"—fast, responsive, and smart. By using Memoization to save CPU cycles and Layout Effects to handle scrolling, you can build an interface that feels like a native app.


✨ Let's keep the conversation going!

If you found this interesting, I'd love for you to check out more of my work or just drop in to say hello.

✍️ Read more on my blog: bishoy-bishai.github.io

Let's chat on LinkedIn: linkedin.com/in/bishoybishai

📘 Curious about AI?:
You can also check out my book:
Surrounded by AI


Top comments (0)