robtaussig / react-use-websocket

React Hook for WebSocket communication
MIT License
1.6k stars 136 forks source link

Any way to for the receiver to receive when the sender is typing? #240

Open jonny-dungeons opened 3 months ago

jonny-dungeons commented 3 months ago

In a scenario where you have 2 users chatting is there any way for the receiver to know when the sender is typing using your hook?

jonny-dungeons commented 3 months ago

Anybody using this hook library? If so I'd like to know if there is a way for sender to emit/broadcast to receiver. This would help me build the typing indicator that lets receiver see that user is typing.

Mark-Elliott5 commented 2 months ago

You need a server in the middle to relay messages to other Websocket connections. Use webRTC if you want a peer to peer connection.

Here's a barebones example that sends a 'typing' indicator to the server, which then relays that back to other connected clients, which then displays who is typing within the same component (a chat application would need to be more fleshed out in reality). The component only sends when e.target.value.length changes from 0 to 1 or 1 to 0, to stop unnecessary messages:

Component:

import useWebSocket from 'react-use-websocket';
import { useRef } from 'react';

interface ITypingIndication {
  action: 'typing';
  typing: boolean;
}

interface ITypingMessage {
  type: 'typing';
  typing: boolean;
  user: string;
}

function MessageForm() {
  const { sendMessage, readyState, lastMessage } = useWebSocket(`wss://${window.location.host}/chat`);

  const typingMessage: ITypingMessage = (() => {
    try {
      return JSON.parse(lastMessage);
    } catch (e) {
      return { type: 'typing', typing: false, user: ''}
    }
  })();

  const lastTypingSent = useRef(false);

  const handleTyping: React.ChangeEventHandler<HTMLTextAreaElement> = (e) => {
    if (readyState !== 1) {
      return;
    }
    const typing = !!e.target.value;
    if (lastTypingSent.current === typing) {
      return;
    }
    const data: ITypingIndication = {
      action: 'typing',
      typing,
    };
    lastTypingSent.current = typing;
    sendMessage(JSON.stringify(data));
  };

  return (
    <>
       {lastMessage.type === "typing" && lastMessage.typing && (
          <span>{lastMessage.username} is typing...</span>
        )}
      <textarea onChange={handleTyping} />
    </>
  )
}

Server (express handler):

import WebSocket from 'ws';
import { INext, IReq, IRes } from '../types/express';

type UserAction = ITypingIndicator // | (other types of user actions messages)

interface ITypingMessage {
  type: 'typing';
  typing: boolean;
  user: string;
}

const sockets = [];

function websocketHandler(ws: WebSocket, req: IReq, next: INext) {
  sockets.push(ws);

  ws.on('message', (msg: WebSocket.RawData) => {
    if (!req.user) {
      return;
    }
    const data: UserActions = JSON.parse(msg.toString());
    const { action } = data;
    if (action === 'typing') {
      try {
        const { username } = req.user;
        const typingMessage: ITypingMessage = {
          type: 'typing',
          typing,
          username,
        };
        const jsonString = JSON.stringify(typingMessage);
        sockets.forEach((ws) => ws.send(jsonString));
    } catch (error) {
        console.log(error);
    }
  }