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:
-
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.
-
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).
-
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
connectionStatusshould reflect 'open'. - When messages are received, the
latestMessagestate should update, and any registeredonMessagecallbacks should be invoked. - When an error occurs,
connectionStatusshould be 'error', andonErrorcallbacks should be invoked. - When the connection closes,
connectionStatusshould be 'closed', andonClosecallbacks should be invoked. - The
sendMessagefunction should send data over the open WebSocket connection. - The
reconnectfunction 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
messageevents 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:nullinitially, then updates with received message objects.error:nullinitially, then anEventobject 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
sendMessageandreconnectfunctions 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
useRefto store the WebSocket instance and ensure it's accessible across renders without causing re-renders when it changes. - Think about how to manage the
connectionStatusstate effectively. - For message event handlers, use the standard
MessageEventinterface. - The
errorreturned by the hook should likely be anEventobject or a more specific error type. - Consider the implications of
sendMessagebeing called before the connection is fullyopen. 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.