A generic React hook for real-time event listening with any socket implementation (Socket.IO, WebSocket, etc.).
No more vendor lock-in! Works with Frappe/ERPNext, custom backends, or any real-time system.
- π Works with any socket implementation (Socket.IO, WebSocket, custom)
- π― Zero dependencies - no vendor lock-in
- π Backward compatible with existing Frappe/ERPNext projects
- π Auto-reconnection with exponential backoff
- π Connection statistics and monitoring
- ποΈ Event filtering and custom subscriptions
- π Retry logic for failed operations
- π Debug mode for development
- π Performance monitoring
- β‘ Simple React Hook API
- π¦ Ready-to-use NPM package
- π§ Built-in adapters for popular socket libraries
- π Comprehensive TypeScript support
npm install react-socket-listener
# or
yarn add react-socket-listener
npm install react-socket-listener --legacy-peer-deps
# or
yarn add react-socket-listener --legacy-peer-deps
For Socket.IO support:
npm install socket.io-client
For Frappe/ERPNext support:
npm install frappe-react-sdk
If you're getting React version conflicts:
-
Clear npm cache:
npm cache clean --force
-
Use legacy peer deps:
npm install react-socket-listener --legacy-peer-deps
-
Force installation (if needed):
npm install react-socket-listener --force
-
Check your React version:
npm list react
This package supports React 17, 18, and 19.
import React from "react";
import { useGenericEventListener, createSocketIOAdapter } from "react-socket-listener";
import { FrappeContext, FrappeConfig } from 'frappe-react-sdk';
import { useContext } from "react";
function CustomerList() {
// Create socket adapter
const {socket} = useContext(FrappeContext) as FrappeConfig;
const {
viewers,
socketStatus,
isConnected,
emitEvent,
getStats
} = useGenericEventListener(
socket,
{ doctype: "Customer" },
{
debug: true,
autoReconnect: true,
maxReconnectAttempts: 5
},
(event) => {
console.log("π© Incoming event:", event);
}
);
return (
<div>
<h2>Customer Monitor</h2>
<p>π Status: {socketStatus} {isConnected ? "π’" : "π΄"}</p>
<p>π₯ Viewers: {viewers.join(", ") || "None"}</p>
<p>π Stats: {JSON.stringify(getStats())}</p>
<button onClick={() => emitEvent("custom_event", { message: "Hello!" })}>
π Emit Event
</button>
</div>
);
}
import React from "react";
import { useGenericEventListener, createSocketIOAdapter } from "react-socket-listener";
function CustomerList() {
// Create socket adapter
const socket = createSocketIOAdapter("http://localhost:8000");
const {
viewers,
socketStatus,
isConnected,
emitEvent,
getStats
} = useGenericEventListener(
socket,
{ doctype: "Customer" },
{
debug: true,
autoReconnect: true,
maxReconnectAttempts: 5
},
(event) => {
console.log("π© Incoming event:", event);
}
);
return (
<div>
<h2>Customer Monitor</h2>
<p>π Status: {socketStatus} {isConnected ? "π’" : "π΄"}</p>
<p>π₯ Viewers: {viewers.join(", ") || "None"}</p>
<p>π Stats: {JSON.stringify(getStats())}</p>
<button onClick={() => emitEvent("custom_event", { message: "Hello!" })}>
π Emit Event
</button>
</div>
);
}
import React from "react";
import { useGenericEventListener, createWebSocketAdapter } from "react-socket-listener";
function RealTimeApp() {
const socket = createWebSocketAdapter("ws://localhost:8080");
const { socketStatus, emitEvent } = useGenericEventListener(
socket,
{ doctype: "Orders", customEvents: ["order_updated", "payment_received"] },
{ debug: true }
);
return (
<div>
<p>Status: {socketStatus}</p>
<button onClick={() => emitEvent("ping", { timestamp: Date.now() })}>
Ping Server
</button>
</div>
);
}
import React from "react";
import { useGenericEventListener, GenericSocket } from "react-socket-listener";
// Your custom socket implementation
class MyCustomSocket implements GenericSocket {
connected = false;
// ... implement all required methods
}
function CustomApp() {
const socket = new MyCustomSocket();
const { socketStatus, emitEvent } = useGenericEventListener(
socket,
{ doctype: "Products" }
);
return <div>Status: {socketStatus}</div>;
}
import React from "react";
import { useFrappeEventListener } from "react-socket-listener";
function CustomerList() {
const { viewers, socketStatus, emitEvent } = useFrappeEventListener(
"Customer",
(event) => {
console.log("π© Incoming event:", event);
}
);
return (
<div>
<h2>Customer Monitor</h2>
<p>π Socket status: {socketStatus}</p>
<p>π₯ Viewers: {viewers.join(", ") || "None"}</p>
</div>
);
}
Feature | Current frappeEventListener.tsx | react-socket-listener |
---|---|---|
Socket Support | Frappe-specific only | β Generic (Socket.IO, WebSocket, custom) |
Auto-reconnection | β Basic | β Advanced with exponential backoff |
Connection Monitoring | β Basic status | β Comprehensive stats & monitoring |
Event Filtering | β None | β Advanced filtering options |
Debug Mode | β Commented out | β Built-in debug logging |
Retry Logic | β None | β Configurable retry mechanism |
TypeScript Support | β Basic | β Full TypeScript definitions |
Performance Monitoring | β None | β Built-in performance stats |
Vendor Lock-in | β Frappe-only | β Zero dependencies, vendor-agnostic |
Parameter | Type | Required | Description |
---|---|---|---|
socket |
GenericSocket | null |
β | Socket instance (use adapters or implement GenericSocket) |
initialSubscription |
SubscriptionOptions |
β | Initial subscription configuration |
config |
EventListenerConfig |
β | Event listener configuration |
onEventCallback |
function |
β | Callback for incoming events |
{
viewers: string[]; // Active viewers
socketStatus: SocketStatus; // Connection status
isConnected: boolean; // Connection state
emitEvent: (event: string, data?: any) => Promise<boolean>; // Emit events
subscribe: (options: SubscriptionOptions) => void; // Subscribe to events
unsubscribe: (doctype: string) => void; // Unsubscribe
getStats: () => ConnectionStats; // Get statistics
reconnect: () => void; // Manual reconnect
clearListeners: () => void; // Clear all listeners
}
interface EventListenerConfig {
autoReconnect?: boolean; // Auto-reconnect on disconnect
maxReconnectAttempts?: number; // Max reconnection attempts
reconnectDelay?: number; // Delay between reconnects (ms)
debug?: boolean; // Enable debug logging
eventFilters?: { // Event filtering
eventTypes?: string[];
doctypes?: string[];
};
retryConfig?: { // Retry configuration
maxAttempts?: number;
delay?: number;
};
}
import { createSocketIOAdapter, createFrappeSocketAdapter } from "react-socket-listener";
// Basic Socket.IO
const socket = createSocketIOAdapter("http://localhost:8000");
// Frappe-compatible Socket.IO
const frappeSocket = createFrappeSocketAdapter("http://localhost:8000", {
token: "your-auth-token",
userId: "user@example.com"
});
import { createWebSocketAdapter } from "react-socket-listener";
const socket = createWebSocketAdapter("ws://localhost:8080");
- Frappe/ERPNext real-time updates
- Custom backend real-time features
- Multi-tenant applications
- IoT device monitoring
- Chat applications
- Live dashboards
- Collaborative editing
- React β₯ 17
- TypeScript (optional but recommended)
Solution: Install the optional dependency:
npm install socket.io-client
Solution: Install the optional dependency:
npm install frappe-react-sdk
Solution: Use legacy peer deps:
npm install react-socket-listener --legacy-peer-deps
Solution: Make sure you have TypeScript installed:
npm install -D typescript @types/react
Solution: Check your server URL and ensure the server is running:
// Debug connection
const { debugInfo, connectionError } = useSocketConnection();
console.log('Debug info:', debugInfo);
console.log('Connection error:', connectionError);
PRs and issues are welcome!
Please open an issue first to discuss what you'd like to change.
MIT Β© 2025 Abhishek-Chougule