Skip to main content

Event Names

  • typing - Indicate that a user is typing
  • stop-typing - Indicate that a user stopped typing

Description

Typing indicators provide real-time feedback to users in a pull request room, showing when someone is composing a message. This enhances the chat experience by making the conversation feel more interactive.

Connection Setup

First, establish a WebSocket connection and join a room:
import { io } from 'socket.io-client';

const socket = io('https://api.diffy.com', {
  transports: ['websocket']
});

socket.on('connect', () => {
  // Join the room first
  socket.emit('join-pr-room', {
    pullRequestId: 123,
    username: 'johndoe'
  });
});

Typing Event

Event Name

typing

Payload

pullRequestId
number
required
The ID of the pull request room
username
string
required
The username of the user who is typing

Request Example

socket.emit('typing', {
  pullRequestId: 123,
  username: 'johndoe'
});

Server Response

The server broadcasts the typing indicator to all users in the room.
username
string
The username of the user who is typing

Listening for Typing Events

socket.on('typing', (data) => {
  console.log(`${data.username} is typing...`);
});

Stop Typing Event

Event Name

stop-typing

Payload

pullRequestId
number
required
The ID of the pull request room
username
string
required
The username of the user who stopped typing

Request Example

socket.emit('stop-typing', {
  pullRequestId: 123,
  username: 'johndoe'
});

Server Response

The server broadcasts the stop-typing indicator to all users in the room.
username
string
The username of the user who stopped typing

Listening for Stop Typing Events

socket.on('stop-typing', (data) => {
  console.log(`${data.username} stopped typing`);
});

Complete Example

import { io } from 'socket.io-client';

const socket = io('https://api.diffy.com', {
  transports: ['websocket']
});

const pullRequestId = 123;
const username = 'johndoe';
let typingTimeout;

socket.on('connect', () => {
  console.log('Connected to WebSocket server');
  
  socket.emit('join-pr-room', {
    pullRequestId,
    username
  });
});

// Listen for typing indicators
socket.on('typing', (data) => {
  if (data.username !== username) {
    console.log(`${data.username} is typing...`);
  }
});

socket.on('stop-typing', (data) => {
  if (data.username !== username) {
    console.log(`${data.username} stopped typing`);
  }
});

// Handle user typing in input field
function handleTyping() {
  // Emit typing event
  socket.emit('typing', {
    pullRequestId,
    username
  });
  
  // Clear existing timeout
  clearTimeout(typingTimeout);
  
  // Set timeout to emit stop-typing after 1 second of inactivity
  typingTimeout = setTimeout(() => {
    socket.emit('stop-typing', {
      pullRequestId,
      username
    });
  }, 1000);
}

// Attach to input field
const messageInput = document.getElementById('message-input');
messageInput.addEventListener('input', handleTyping);

React Example

import { useEffect, useState, useRef } from 'react';
import { io } from 'socket.io-client';

function PRChat({ pullRequestId, username }) {
  const [socket, setSocket] = useState(null);
  const [typingUsers, setTypingUsers] = useState(new Set());
  const [inputMessage, setInputMessage] = useState('');
  const typingTimeoutRef = useRef(null);

  useEffect(() => {
    const newSocket = io('https://api.diffy.com', {
      transports: ['websocket']
    });

    newSocket.on('connect', () => {
      newSocket.emit('join-pr-room', {
        pullRequestId,
        username
      });
    });

    newSocket.on('typing', (data) => {
      if (data.username !== username) {
        setTypingUsers((prev) => new Set([...prev, data.username]));
      }
    });

    newSocket.on('stop-typing', (data) => {
      setTypingUsers((prev) => {
        const updated = new Set(prev);
        updated.delete(data.username);
        return updated;
      });
    });

    setSocket(newSocket);

    return () => {
      newSocket.emit('leave-pr-room', { pullRequestId, username });
      newSocket.close();
    };
  }, [pullRequestId, username]);

  const handleInputChange = (e) => {
    const value = e.target.value;
    setInputMessage(value);

    if (socket && value.trim()) {
      // Emit typing event
      socket.emit('typing', {
        pullRequestId,
        username
      });

      // Clear existing timeout
      if (typingTimeoutRef.current) {
        clearTimeout(typingTimeoutRef.current);
      }

      // Set timeout to emit stop-typing after 1 second
      typingTimeoutRef.current = setTimeout(() => {
        socket.emit('stop-typing', {
          pullRequestId,
          username
        });
      }, 1000);
    }
  };

  const sendMessage = () => {
    if (socket && inputMessage.trim()) {
      // Stop typing indicator when message is sent
      socket.emit('stop-typing', {
        pullRequestId,
        username
      });
      
      socket.emit('send-message-to-pr-room', {
        pullRequestId,
        username,
        message: inputMessage
      });
      
      setInputMessage('');
      
      if (typingTimeoutRef.current) {
        clearTimeout(typingTimeoutRef.current);
      }
    }
  };

  return (
    <div>
      <div className="typing-indicator">
        {typingUsers.size > 0 && (
          <p>
            {Array.from(typingUsers).join(', ')} 
            {typingUsers.size === 1 ? ' is' : ' are'} typing...
          </p>
        )}
      </div>
      <input
        value={inputMessage}
        onChange={handleInputChange}
        onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
        placeholder="Type a message..."
      />
      <button onClick={sendMessage}>Send</button>
    </div>
  );
}

Best Practices

Debouncing

Implement a debounce mechanism to avoid sending too many typing events:
  • Emit typing when the user starts typing
  • Use a timeout (typically 1 second) to automatically emit stop-typing after inactivity
  • Clear and reset the timeout on each keystroke

Filtering Self

Always filter out your own typing indicators on the client side:
socket.on('typing', (data) => {
  if (data.username !== currentUsername) {
    // Show typing indicator
  }
});

Cleanup

Always emit stop-typing when:
  • The user sends a message
  • The user navigates away from the page
  • The user leaves the room
  • The input field loses focus (optional)

Room Architecture

Typing indicators are broadcast only to users who have joined the specific pull request room. The room identifier follows the pattern pr:{pullRequestId}.

Notes

  • You must join a room using join-pr-room before sending typing indicators
  • Typing indicators are broadcast to all users in the room, including the sender (filter on client side)
  • These events do not persist; they’re only for real-time feedback
  • Consider implementing a timeout mechanism to automatically clear typing indicators
  • Typing indicators improve user experience but should not be relied upon for critical functionality

Build docs developers (and LLMs) love