npm package: @four-leaf-studios/rl-socket-hook
Description: A tiny React wrapper around a Rocket League WebSocket plugin (`ws://localhost:49122`). It provides:
Latest version: 1.2.6

# @four-leaf-studios/rl-socket-hook

A tiny React wrapper around a Rocket League WebSocket plugin (`ws://localhost:49122`). It provides:

- **`<RLProvider>`**: React context provider for WebSocket events.
- **`useEvent`**: Subscribe to entire event payloads.
- **`useEventSelector`**: Subscribe to specific slices of event payloads.
- **`useRocketLeagueSocket`**: Low-level hook for raw event data.

## Table of Contents

1. [Installation](#installation)
2. [Quick Start](#quick-start)
3. [Core API](#core-api)
   - [RLProvider](#rlprovider)
   - [useEvent](#useevent)
   - [useEventSelector](#useeventselector)
   - [useRocketLeagueSocket](#userocketleaguesocket)
4. [Examples](#examples)
   - [Overlay Component](#overlay-component)
   - [Scoreboard](#scoreboard)
   - [Raw Event Dump](#raw-event-dump)
5. [TypeScript Support](#typescript-support)
   - [Built-in Types](#built-in-types)
   - [Extending EventPayloads](#extending-eventpayloads)
6. [Contributing](#contributing)
7. [License](#license)

## Installation

```bash
npm install @four-leaf-studios/rl-socket-hook
# or
yarn add @four-leaf-studios/rl-socket-hook
```

## Quick Start

```tsx
import {
  RLProvider,
  useEvent,
  useEventSelector,
} from "@four-leaf-studios/rl-socket-hook";

function App() {
  return (
    <RLProvider url="ws://localhost:49122">
      <Scoreboard />
      <Overlay />
    </RLProvider>
  );
}
```

## Core API

### `RLProvider`

Wrap your app to initialize the WebSocket connection and provide context.

```tsx
<RLProvider url="ws://localhost:49122">{children}</RLProvider>
```

- **`url`** (string): WebSocket URL. Default: `ws://localhost:49122`.
- **`children`**: React nodes.

---

### `useEvent(eventName)`

Subscribe to the full payload of an event.

```tsx
const gameState = useEvent("game:update_state");
```

- **Parameters**:
  - `eventName`: key from `EventPayloads`.
- **Returns**: Payload object or `undefined`.

---

### `useEventSelector(eventName, selector, isEqual?)`

Subscribe to a specific slice of the payload to minimize re-renders.

```tsx
const blueScore = useEventSelector(
  "game:update_state",
  (s) => s.game.teams[0].score ?? 0
);
```

- **Parameters**:
  - `eventName`: string key.
  - `selector`: `(payload) => slice`.
  - `isEqual?`: optional custom equality function.
- **Returns**: Selected slice value.

---

### `useRocketLeagueSocket`

> **Note:** This hook **opens its own WebSocket connection**. If you are already using `<RLProvider>`, **do not** use `useRocketLeagueSocket` together—it will create a second connection. Instead, use `useEvent` or `useEventSelector` within the RLProvider context. Use `useRocketLeagueSocket` only when you need a standalone socket (e.g., in a custom provider).

```tsx
import { useRocketLeagueSocket } from "@four-leaf-studios/rl-socket-hook";

function CustomProviderComponent() {
  const allEvents = useRocketLeagueSocket();
  // ...custom handling...
}
```

- **Returns**: An object mapping event names to payloads: `{ [eventName]: payload }`.

## Examples

### Overlay Component

Full overlay UI with scores and players.

```tsx
function Overlay() {
  const gameState = useEvent("game:update_state");
  if (!gameState) return null;

  const time = formatTime(gameState.game.time_seconds);
  const blueScore = gameState.game.teams[0].score;
  const orangeScore = gameState.game.teams[1].score;
  // ...
}
```

---

### Scoreboard

Render only necessary fields:

```tsx
function Scoreboard() {
  const blueScore = useEventSelector(
    "game:update_state",
    (s) => s.game.teams[0].score ?? 0
  );
  const orangeScore = useEventSelector(
    "game:update_state",
    (s) => s.game.teams[1].score ?? 0
  );
  const time = useEventSelector(
    "game:update_state",
    (s) => s.game.time_seconds ?? 0
  );

  return (
    <div className="flex items-center space-x-4 p-4 bg-neutral-800 text-white">
      <span>🔵 {blueScore}</span>
      <span>⏱️ {formatTime(time)}</span>
      <span>🟠 {orangeScore}</span>
    </div>
  );
}
```

---

### Raw Event Dump

Debug all incoming events:

````tsx
function RawDump() {
  const { events } = useRocketLeagueSocket();
  return <pre>{JSON.stringify(events, null, 2)}</pre>;
}
```tsx
function RawDump() {
  const data = useRocketLeagueSocket();
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
````

---

### WebsocketData Component

A React example showing full usage of `useRocketLeagueSocket` (connection state, errors, and events rendering).

```tsx
// src/WebsocketData.tsx
import React from "react";
import useRocketLeagueSocket from "./useRocketLeagueSocket";
import { PayloadStorage } from "./types";

const STATE_LABELS = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"] as const;

export const WebsocketData: React.FC = () => {
  const { events, readyState, error } = useRocketLeagueSocket<PayloadStorage>();

  return (
    <div style={{ fontFamily: "monospace", padding: "1rem" }}>
      <h1>Rocket League Live Events</h1>

      <p>
        <strong>Connection:</strong> {STATE_LABELS[readyState] ?? readyState}
      </p>
      {error && (
        <p style={{ color: "red" }}>
          <strong>Error:</strong> {String(error)}
        </p>
      )}

      {Object.entries(events).map(([event, payload]) => (
        <div
          key={event}
          style={{
            border: "1px solid #ccc",
            marginBottom: "1rem",
            padding: "0.5rem",
            borderRadius: "4px",
            backgroundColor: "#f9f9f9",
          }}
        >
          <h2>{event}</h2>
          <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
            {JSON.stringify(payload, null, 2)}
          </pre>
        </div>
      ))}

      {Object.keys(events).length === 0 && <p>No events received yet.</p>}
    </div>
  );
};

export default WebsocketData;
```

---

### Send Event Example

Demonstration of sending a custom event once the socket is open:

```tsx
import React, { useEffect } from "react";
import { useRocketLeagueSocket } from "@four-leaf-studios/rl-socket-hook";

function SendEventExample() {
  const { send, readyState } = useRocketLeagueSocket();

  useEffect(() => {
    if (readyState === WebSocket.OPEN) {
      send("my:custom_event", { foo: "bar" });
    }
  }, [readyState, send]);

  return <div>Sent "my:custom_event" when connected.</div>;
}
```

## TypeScript Support

### Built-in Types

All event payloads are typed in the `EventPayloads` interface. Import as needed:

```ts
import type {
  GameUpdateState,
  GoalScoredEvent,
  EventPayloads,
} from "@four-leaf-studios/rl-socket-hook";
```

### Extending `EventPayloads`

Use declaration merging to override or extend event types:

```ts
// src/types/rl-socket-hook.d.ts
import type { GameUpdateState } from "@four-leaf-studios/rl-socket-hook";

declare module "@four-leaf-studios/rl-socket-hook" {
  interface EventPayloads {
    "game:update_state": GameUpdateState;
    "my:custom_event": { foo: string };
  }
}
```

## Contributing

PRs welcome. Please open issues for features or bugs.

## License

MIT © Four Leaf Studios