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.
npm install @four-leaf-studios/rl-socket-hook
# or
yarn add @four-leaf-studios/rl-socket-hookimport {
RLProvider,
useEvent,
useEventSelector,
} from "@four-leaf-studios/rl-socket-hook";
function App() {
return (
<RLProvider url="ws://localhost:49122">
<Scoreboard />
<Overlay />
</RLProvider>
);
}Wrap your app to initialize the WebSocket connection and provide context.
<RLProvider url="ws://localhost:49122">{children}</RLProvider>url(string): WebSocket URL. Default:ws://localhost:49122.children: React nodes.
Subscribe to the full payload of an event.
const gameState = useEvent("game:update_state");- Parameters:
eventName: key fromEventPayloads.
- Returns: Payload object or
undefined.
Subscribe to a specific slice of the payload to minimize re-renders.
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.
Note: This hook opens its own WebSocket connection. If you are already using
<RLProvider>, do not useuseRocketLeagueSockettogether—it will create a second connection. Instead, useuseEventoruseEventSelectorwithin the RLProvider context. UseuseRocketLeagueSocketonly when you need a standalone socket (e.g., in a custom provider).
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 }.
Full overlay UI with scores and players.
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;
// ...
}Render only necessary fields:
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>
);
}Debug all incoming events:
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>;
}A React example showing full usage of useRocketLeagueSocket (connection state, errors, and events rendering).
// 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;Demonstration of sending a custom event once the socket is open:
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>;
}All event payloads are typed in the EventPayloads interface. Import as needed:
import type {
GameUpdateState,
GoalScoredEvent,
EventPayloads,
} from "@four-leaf-studios/rl-socket-hook";Use declaration merging to override or extend event types:
// 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 };
}
}PRs welcome. Please open issues for features or bugs.
MIT © Four Leaf Studios