npm package: @gedeagas/react-universal-websocket
Description: Universal Websocket Hooks for React & React Native
Author: Agastya Darma Laksana
Latest version: 1.1.6

# react-universal-websocket

`react-universal-websocket` This library is compatible with both React and React Native, offering powerful and flexible React hooks to simplify the management of WebSocket connections. It features seamless WebSocket integration within your applications, including capabilities like automatic reconnection, offline message queuing, and the ability to share WebSocket connections across components. Additionally, it provides typed support for both TypeScript and Flow, ensuring robust type safety for your projects.

🇨🇳 [中文文档](readme.cn.md)

## Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [Options](#options)
- [API](#api)
- [Types](#types)
- [Advanced Usage](#advanced-usage)
  - [Sharing WebSocket Connections](#sharing-websocket-connections)
  - [Handling Heartbeats](#handling-heartbeats)
  - [Custom Reconnection Logic](#custom-reconnection-logic)
- [Contributing](#contributing)
- [License](#license)

## Installation

To install the `useWebSocket` hook, you can use npm or yarn:

```sh
npm install @gedeagas/react-universal-websocket
```

Or using Yarn:

```sh
yarn add @gedeagas/react-universal-websocket
```

## Usage

Here's a basic example of how to use the `useWebSocket` hook in a React component:

```jsx
import React, { useEffect } from 'react';
import { useWebSocket, ReadyState } from '@gedeagas/react-universal-websocket';

const WebSocketExample = () => {
  const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket');

  useEffect(() => {
    if (lastMessage !== null) {
      console.log('Received message:', lastMessage.data);
    }
  }, [lastMessage]);

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

  const connectionStatus = {
    [ReadyState.CONNECTING]: 'Connecting',
    [ReadyState.OPEN]: 'Open',
    [ReadyState.CLOSING]: 'Closing',
    [ReadyState.CLOSED]: 'Closed',
    [ReadyState.UNINSTANTIATED]: 'Uninstantiated',
  }[readyState];

  return (
    <div>
      <button onClick={handleClick}>Send Message</button>
      <p>Connection Status: {connectionStatus}</p>
      {lastMessage && <p>Last message: {lastMessage.data}</p>}
    </div>
  );
};

export default WebSocketExample;
```

## Options

The `useWebSocket` hook accepts a configuration object with various options to customize the behavior of the WebSocket connection:

- `queryParams` (QueryParams): An object containing query parameters to append to the WebSocket URL.
- `protocols` (string | string[]): A string or array of strings representing the subprotocols.
- `share` (boolean): If `true`, allows sharing of the WebSocket instance among multiple hooks.
- `onOpen` (function): A callback function for when the WebSocket connection opens.
- `onClose` (function): A callback function for when the WebSocket connection closes.
- `onMessage` (function): A callback function for handling incoming messages.
- `onError` (function): A callback function for handling errors.
- `onReconnectStop` (function): A callback function called when reconnection attempts stop.
- `shouldReconnect` (function): A function that determines whether to attempt reconnection.
- `reconnectInterval` (number | function): The interval between reconnection attempts.
- `reconnectAttempts` (number): The maximum number of reconnection attempts allowed.
- `filter` (function): A function to filter incoming messages.
- `retryOnError` (boolean): If `true`, retries connection on error.
- `skipAssert` (boolean): If `true`, skips WebSocket assertion checks.
- `heartbeat` (boolean | HeartbeatOptions): Enables heartbeat messages to keep the connection alive.

Here's a comprehensive documentation for the `useWebSocket` hook options, along with examples for each. This should help developers understand and utilize the different configuration options available.

### `queryParams`
An object containing query parameters to append to the WebSocket URL.

- **Type:** `{ [key: string]: string | number }`

**Example:**
```jsx
const options = {
  queryParams: { token: 'abcd1234' },
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `protocols`
A string or array of strings representing the subprotocols.

- **Type:** `string | string[]`

**Example:**
```jsx
const options = {
  protocols: ['protocolOne', 'protocolTwo'],
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `share`
If `true`, allows sharing of the WebSocket instance among multiple hooks.

- **Type:** `boolean`
- **Default:** `false`

**Example:**
```jsx
const options = {
  share: true,
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `onOpen`
A callback function for when the WebSocket connection opens.

- **Type:** `(event: WebSocketEventMap["open"]) => void`

**Example:**
```jsx
const options = {
  onOpen: (event) => console.log('Connection opened!', event),
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `onClose`
A callback function for when the WebSocket connection closes.

- **Type:** `(event: WebSocketEventMap["close"]) => void`

**Example:**
```jsx
const options = {
  onClose: (event) => console.log('Connection closed!', event),
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `onMessage`
A callback function for handling incoming messages.

- **Type:** `(event: WebSocketEventMap["message"]) => void`

**Example:**
```jsx
const options = {
  onMessage: (event) => console.log('Message received!', event.data),
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `onError`
A callback function for handling errors.

- **Type:** `(event: WebSocketEventMap["error"]) => void`

**Example:**
```jsx
const options = {
  onError: (event) => console.log('Error occurred!', event),
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `onReconnectStop`
A callback function called when reconnection attempts stop.

- **Type:** `(numAttempts: number) => void`

**Example:**
```jsx
const options = {
  onReconnectStop: (numAttempts) => console.log(`Reconnection stopped after ${numAttempts} attempts`),
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `shouldReconnect`
A function that determines whether to attempt reconnection.

- **Type:** `(event: WebSocketEventMap["close"]) => boolean`

**Example:**
```jsx
const options = {
  shouldReconnect: (event) => true, // Always attempt to reconnect
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `reconnectInterval`
The interval between reconnection attempts.

- **Type:** `number | (lastAttemptNumber: number) => number`

**Example:**
```jsx
const options = {
  reconnectInterval: 5000, // 5 seconds
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `reconnectAttempts`
The maximum number of reconnection attempts allowed.

- **Type:** `number`

**Example:**
```jsx
const options = {
  reconnectAttempts: 3, // Max 3 reconnection attempts
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `filter`
A function to filter incoming messages.

- **Type:** `(message: WebSocketEventMap["message"]) => boolean`

**Example:**
```jsx
const options = {
  filter: (message) => message.data !== 'ignore', // Ignore messages with data 'ignore'
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `retryOnError`
If `true`, retries connection on error.

- **Type:** `boolean`
- **Default:** `false`

**Example:**
```jsx
const options = {
  retryOnError: true,
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `skipAssert`
If `true`, skips WebSocket assertion checks.

- **Type:** `boolean`
- **Default:** `false`

**Example:**
```jsx
const options = {
  skipAssert: true,
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### `heartbeat`
Enables heartbeat messages to keep the connection alive.

- **Type:** `boolean | HeartbeatOptions`

**Example:**
```jsx
const options = {
  heartbeat: {
    message: 'ping',
    returnMessage: 'pong',
    timeout: 3000, // 3 seconds
    interval: 10000, // 10 seconds
  },
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### Complete Usage Example

Here’s a complete example using several options together:

```jsx
import useWebSocket from 'react-use-websocket';

function App() {
  const options = {
    queryParams: { token: 'abcd1234' },
    protocols: ['protocolOne', 'protocolTwo'],
    share: true,
    onOpen: (event) => console.log('Connection opened!', event),
    onClose: (event) => console.log('Connection closed!', event),
    onMessage: (event) => console.log('Message received!', event.data),
    onError: (event) => console.log('Error occurred!', event),
    onReconnectStop: (numAttempts) => console.log(`Reconnection stopped after ${numAttempts} attempts`),
    shouldReconnect: (event) => true, // Always attempt to reconnect
    reconnectInterval: (attemptNumber) => Math.min(Math.pow(2, attemptNumber) * 1000, 10000), // Exponential backoff
    reconnectAttempts: 10, // Max 10 reconnection attempts
    filter: (message) => message.data !== 'ignore', // Ignore messages with data 'ignore'
    retryOnError: true,
    skipAssert: true,
    heartbeat: {
      message: 'ping',
      returnMessage: 'pong',
      timeout: 3000, // 3 seconds
      interval: 10000, // 10 seconds
    },
  };

  const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);

  return (
    <div>
      <p>Ready State: {readyState}</p>
      <p>Last Message: {lastMessage ? lastMessage.data : 'No message yet'}</p>
      <button onClick={() => sendMessage('Hello WebSocket!')}>Send Message</button>
    </div>
  );
}

export default App;
```

This documentation and the accompanying examples should help you understand and leverage the various options provided by the `useWebSocket` hook.

## API

The `useWebSocket` hook provides the following API:

- `sendMessage`: A function to send a message to the WebSocket server.
- `sendJsonMessage`: A function to send a JSON message to the WebSocket server.
- `lastMessage`: The last message received from the WebSocket server.
- `lastJsonMessage`: The last parsed JSON message received from the WebSocket server.
- `readyState`: The current state of the WebSocket connection.
- `getWebSocket`: A function to get the current WebSocket instance.

## Types

- **QueryParams**: An object mapping strings or numbers to query parameter keys.
- **WebSocketMessage**: Types of messages that can be sent: `string`, `ArrayBuffer`, `SharedArrayBuffer`, `Blob`, or `ArrayBufferView`.
- **SendMessage**: A function type to send WebSocket messages.
- **SendJsonMessage**: A function type to send JSON messages.
- **WebSocketHook**: The hook return type, including methods and state properties related to the WebSocket connection.

## Advanced Usage

### Sharing WebSocket Connections

To share a WebSocket connection among multiple components, set the `share` option to `true`. This ensures that a single WebSocket instance is reused, reducing the overhead of multiple connections.

```jsx
const options = {
  share: true,
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### Handling Heartbeats

Heartbeats can keep the connection alive by sending periodic messages. Use the `heartbeat` option to enable and configure heartbeats:

```jsx
const options = {
  heartbeat: {
    message: 'ping', // Message sent as heartbeat
    returnMessage: 'pong', // Expected return message
    timeout: 10000, // Time to wait for return message
    interval: 30000, // Interval between heartbeats
  },
};

const { sendMessage, lastMessage, readyState } = useWebSocket('ws://example.com/socket', options);
```

### Custom Reconnection Logic

When dealing with WebSocket connections, it's important to handle reconnections gracefully to ensure reliability. This can be achieved using the `shouldReconnect`, `reconnectInterval`, and `reconnectAttempts` options.

Here's an example of how you can configure these options:

### Exponential Backoff

Alternatively, you can provide a function for `reconnectInterval` that accepts the nth last attempt as a parameter and returns a number. This can be useful for employing more advanced reconnect strategies like Exponential Backoff:

```jsx
const exponentialBackoffOptions = {
  shouldReconnect: (closeEvent) => true,
  reconnectAttempts: 10, // Max 10 reconnection attempts
  // attemptNumber starts at 0, resulting in a pattern of 1 second, 2 seconds, 4 seconds, 8 seconds, and then caps at 10 seconds
  reconnectInterval: (attemptNumber) =>
    Math.min(Math.pow(2, attemptNumber) * 1000, 10000),
};

const { sendMessage, lastMessage, readyState } = useWebSocket(
  'wss://echo.websocket.org',
  exponentialBackoffOptions
);
```

### Fibonacci Backoff

Another algorithm for reconnection is the Fibonacci Backoff, which uses the Fibonacci sequence to determine the wait time between reconnections. Here's how you can implement it:

```jsx
const fibonacciBackoffOptions = {
  shouldReconnect: (closeEvent) => true,
  reconnectAttempts: 10, // Max 10 reconnection attempts
  reconnectInterval: (attemptNumber) => {
    const fibonacci = (n) => {
      if (n <= 1) return 1;
      return fibonacci(n - 1) + fibonacci(n - 2);
    };
    // Cap at 60 seconds
    return Math.min(fibonacci(attemptNumber + 1) * 1000, 60000);
  },
};

const { sendMessage, lastMessage, readyState } = useWebSocket(
  'wss://echo.websocket.org',
  fibonacciBackoffOptions
);
```

By customizing the `reconnectInterval` function, you can control the reconnection strategy and improve the reliability and performance of your WebSocket connections based on your application's needs.

## Acknowledgements

This project is a fork of [react-use-websocket](https://github.com/robtaussig/react-use-websocket) and is also inspired by [react-native-reconnecting-websocket](https://github.com/React-Sextant/react-native-reconnecting-websocket). We extend our gratitude to the authors and contributors of these projects for their excellent work and inspiration.

---

## Contributing

Contributions are welcome! Please feel free to submit a pull request or open an issue.

1. Fork the repository.
2. Create a new branch (`git checkout -b feature-branch`).
3. Commit your changes (`git commit -m 'Add new feature'`).
4. Push to the branch (`git push origin feature-branch`).
5. Open a pull request.

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information.