Hone logo
Hone
Problems

React WebSocket Hook Challenge

This challenge asks you to build a reusable React hook, useWebSocket, that simplifies the process of establishing, managing, and interacting with WebSocket connections within a React application. A robust WebSocket hook is crucial for real-time applications, enabling features like live updates, chat functionalities, and collaborative editing.

Problem Description

You need to create a custom React hook named useWebSocket that encapsulates the logic for managing a WebSocket connection. This hook should handle the connection lifecycle, message sending, and message reception.

Key Requirements:

  1. Connection Management:

    • The hook should accept a WebSocket URL as an argument.
    • It should automatically attempt to establish a WebSocket connection when the component using the hook mounts.
    • It should automatically close the WebSocket connection when the component unmounts.
    • It should provide a way to manually reconnect to the WebSocket server.
    • It should handle and report connection errors.
  2. Message Handling:

    • The hook should expose a function to send messages to the WebSocket server.
    • It should store and provide access to the latest received message.
    • It should allow consumers to register callbacks for specific message events (e.g., onMessage, onError, onOpen, onClose).
  3. State Management:

    • The hook should expose the current connection status (e.g., 'connecting', 'open', 'closed', 'error').
    • The hook should expose the latest received message.

Expected Behavior:

  • When initialized with a valid URL, the hook should try to connect.
  • When the connection is open, the connectionStatus should reflect 'open'.
  • When messages are received, the latestMessage state should update, and any registered onMessage callbacks should be invoked.
  • When an error occurs, connectionStatus should be 'error', and onError callbacks should be invoked.
  • When the connection closes, connectionStatus should be 'closed', and onClose callbacks should be invoked.
  • The sendMessage function should send data over the open WebSocket connection.
  • The reconnect function should attempt to re-establish the connection.

Edge Cases to Consider:

  • Invalid WebSocket URL.
  • Network interruptions leading to connection closure.
  • Rapid reconnection attempts.
  • Sending messages before the connection is open.
  • Handling message events that might not be strings (e.g., Blob, ArrayBuffer - though for this challenge, assume string data).

Examples

Let's assume a simple WebSocket server is running at ws://localhost:8080 that echoes messages.

Example 1: Basic Usage

import React from 'react';
import useWebSocket from './useWebSocket'; // Assuming your hook is in './useWebSocket'

function ChatComponent() {
  const {
    latestMessage,
    sendMessage,
    connectionStatus,
    error,
  } = useWebSocket('ws://localhost:8080');

  const handleSendMessage = () => {
    sendMessage('Hello, server!');
  };

  return (
    <div>
      <h2>WebSocket Status: {connectionStatus}</h2>
      {error && <p style={{ color: 'red' }}>Error: {error.message}</p>}
      <p>Last Message: {latestMessage ? latestMessage.data : 'No messages yet'}</p>
      <button onClick={handleSendMessage} disabled={connectionStatus !== 'open'}>
        Send Message
      </button>
    </div>
  );
}

export default ChatComponent;

Input to the hook: 'ws://localhost:8080'

Expected Output from the hook (states):

  • connectionStatus: Initially 'connecting', then potentially 'open', 'error', or 'closed'.
  • latestMessage: null initially, then updates with received message objects.
  • error: null initially, then an Event object if an error occurs.

Explanation: The ChatComponent uses the useWebSocket hook. It displays the connection status and the latest received message. It also provides a button to send a message and a mechanism to display any errors.

Example 2: Handling Callbacks

import React from 'react';
import useWebSocket from './useWebSocket';

function RealtimeCounter() {
  const [count, setCount] = React.useState(0);

  const handleMessage = (event: MessageEvent<any>) => {
    console.log('Received message:', event.data);
    // Assuming the server sends a number as a string
    setCount(parseInt(event.data, 10));
  };

  const handleError = (event: Event) => {
    console.error('WebSocket error:', event);
  };

  const handleOpen = () => {
    console.log('WebSocket connection opened!');
  };

  const handleClose = () => {
    console.log('WebSocket connection closed.');
  };

  useWebSocket('ws://localhost:8080', {
    onMessage: handleMessage,
    onError: handleError,
    onOpen: handleOpen,
    onClose: handleClose,
  });

  return (
    <div>
      <h1>Current Count: {count}</h1>
    </div>
  );
}

export default RealtimeCounter;

Input to the hook: 'ws://localhost:8080', with an options object containing onMessage, onError, onOpen, onClose callbacks.

Expected Behavior: The handleMessage function will be called every time a message is received, updating the count state. handleError, handleOpen, and handleClose will be called on their respective events. The component itself doesn't expose latestMessage or sendMessage but relies on the callbacks.

Example 3: Reconnecting

import React from 'react';
import useWebSocket from './useWebSocket';

function StatusIndicator() {
  const {
    connectionStatus,
    reconnect,
    error,
  } = useWebSocket('ws://localhost:8080', {
    // Optional: Configure reconnection logic if your hook supports it
    // reconnectionAttempts: 5,
    // reconnectionInterval: 2000,
  });

  React.useEffect(() => {
    if (connectionStatus === 'error' || connectionStatus === 'closed') {
      console.log('Attempting to reconnect...');
      reconnect();
    }
  }, [connectionStatus, reconnect]);

  return (
    <div>
      <p>Connection Status: {connectionStatus}</p>
      {error && <p>Error: {error.message}</p>}
      <button onClick={reconnect} disabled={connectionStatus === 'connecting' || connectionStatus === 'open'}>
        Manual Reconnect
      </button>
    </div>
  );
}

export default StatusIndicator;

Input to the hook: 'ws://localhost:8080'

Expected Behavior: The component will automatically attempt to reconnect if the status becomes 'error' or 'closed'. The button allows for manual reconnection.

Constraints

  • The hook should be implemented in TypeScript.
  • The hook must return a stable reference for sendMessage and reconnect functions across renders to avoid unnecessary re-renders in consuming components.
  • The hook should gracefully handle cases where the WebSocket API is not available in the browser environment.
  • Assume the WebSocket server sends messages as strings.

Notes

  • Consider using useRef to store the WebSocket instance and ensure it's accessible across renders without causing re-renders when it changes.
  • Think about how to manage the connectionStatus state effectively.
  • For message event handlers, use the standard MessageEvent interface.
  • The error returned by the hook should likely be an Event object or a more specific error type.
  • Consider the implications of sendMessage being called before the connection is fully open. You might want to buffer messages or ignore the call. For this challenge, simply attempting to send will suffice, but a real-world hook might handle this more robustly.
  • Implementing automatic reconnection with configurable attempts and intervals is a bonus but not strictly required for the core challenge. Focus on manual reconnect first.
Loading editor...
typescript