react-websocket

v2026.09.24

WebSocket integration patterns for React applications. Covers custom hooks, context providers, Socket.IO, and real-time state synchronization with TanStack Query. USE WHEN: user mentions "WebSocket in React", "real-time React", "Socket.IO React", "useWebSocket hook", "live updates", "chat application", "real-time notifications", asks about "how to connect WebSocket in React" DO NOT USE FOR: NestJS WebSocket gateways - use `nestjs-websocket` instead, Spring WebSocket - use `spring-websocket` instead

GitHub
安装命令
npx skhub add claude-dev-suite/react-websocket
Markdown
SKILL.md

React WebSocket Integration

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: react for comprehensive documentation.

Custom WebSocket Hook

import { useState, useEffect, useCallback, useRef } from 'react';

interface UseWebSocketOptions {
  url: string;
  onMessage?: (data: unknown) => void;
  onOpen?: () => void;
  onClose?: () => void;
  onError?: (error: Event) => void;
  reconnect?: boolean;
  reconnectInterval?: number;
  reconnectAttempts?: number;
}

export function useWebSocket({
  url,
  onMessage,
  onOpen,
  onClose,
  onError,
  reconnect = true,
  reconnectInterval = 3000,
  reconnectAttempts = 5,
}: UseWebSocketOptions) {
  const [isConnected, setIsConnected] = useState(false);
  const [lastMessage, setLastMessage] = useState<unknown>(null);
  const wsRef = useRef<WebSocket | null>(null);
  const reconnectCountRef = useRef(0);

  const connect = useCallback(() => {
    const ws = new WebSocket(url);

    ws.onopen = () => {
      setIsConnected(true);
      reconnectCountRef.current = 0;
      onOpen?.();
    };

    ws.onclose = () => {
      setIsConnected(false);
      onClose?.();

      if (reconnect && reconnectCountRef.current < reconnectAttempts) {
        reconnectCountRef.current++;
        setTimeout(connect, reconnectInterval);
      }
    };

    ws.onerror = (error) => {
      onError?.(error);
    };

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      setLastMessage(data);
      onMessage?.(data);
    };

    wsRef.current = ws;
  }, [url, onMessage, onOpen, onClose, onError, reconnect, reconnectInterval, reconnectAttempts]);

  useEffect(() => {
    connect();
    return () => {
      wsRef.current?.close();
    };
  }, [connect]);

  const send = useCallback((data: unknown) => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      wsRef.current.send(JSON.stringify(data));
    }
  }, []);

  const disconnect = useCallback(() => {
    wsRef.current?.close();
  }, []);

  return { isConnected, lastMessage, send, disconnect };
}

Usage Example

function ChatRoom({ roomId }: { roomId: string }) {
  const [messages, setMessages] = useState<Message[]>([]);

  const { isConnected, send } = useWebSocket({
    url: `wss://api.example.com/ws?room=${roomId}`,
    onMessage: (data) => {
      setMessages((prev) => [...prev, data as Message]);
    },
    onOpen: () => {
      send({ type: 'join', room: roomId });
    },
  });

  const handleSend = (text: string) => {
    send({ type: 'message', content: text, room: roomId });
  };

  return (
    <div>
      <div>Status: {isConnected ? 'Connected' : 'Disconnected'}</div>
      <MessageList messages={messages} />
      <MessageInput onSend={handleSend} disabled={!isConnected} />
    </div>
  );
}

Context Provider Pattern

import { createContext, useContext, ReactNode } from 'react';

interface WebSocketContextValue {
  isConnected: boolean;
  send: (data: unknown) => void;
  subscribe: (event: string, handler: (data: unknown) => void) => () => void;
}

const WebSocketContext = createContext<WebSocketContextValue | null>(null);

export function WebSocketProvider({ url, children }: { url: string; children: ReactNode }) {
  const [isConnected, setIsConnected] = useState(false);
  const wsRef = useRef<WebSocket | null>(null);
  const handlersRef = useRef<Map<string, Set<(data: unknown) => void>>>(new Map());

  useEffect(() => {
    const ws = new WebSocket(url);

    ws.onopen = () => setIsConnected(true);
    ws.onclose = () => setIsConnected(false);

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      const handlers = handlersRef.current.get(data.type);
      handlers?.forEach((handler) => handler(data));
    };

    wsRef.current = ws;
    return () => ws.close();
  }, [url]);

  const send = useCallback((data: unknown) => {
    wsRef.current?.send(JSON.stringify(data));
  }, []);

  const subscribe = useCallback((event: string, handler: (data: unknown) => void) => {
    if (!handlersRef.current.has(event)) {
      handlersRef.current.set(event, new Set());
    }
    handlersRef.current.get(event)!.add(handler);

    return () => {
      handlersRef.current.get(event)?.delete(handler);
    };
  }, []);

  return (
    <WebSocketContext.Provider value={{ isConnected, send, subscribe }}>
      {children}
    </WebSocketContext.Provider>
  );
}

export function useWS() {
  const context = useContext(WebSocketContext);
  if (!context) throw new Error('useWS must be used within WebSocketProvider');
  return context;
}

Socket.IO Integration

import { io, Socket } from 'socket.io-client';
import { useEffect, useRef, useState, useCallback } from 'react';

export function useSocketIO(url: string) {
  const [isConnected, setIsConnected] = useState(false);
  const socketRef = useRef<Socket | null>(null);

  useEffect(() => {
    const socket = io(url, {
      auth: { token: getAuthToken() },
      reconnection: true,
      reconnectionAttempts: 5,
    });

    socket.on('connect', () => setIsConnected(true));
    socket.on('disconnect', () => setIsConnected(false));

    socketRef.current = socket;

    return () => {
      socket.disconnect();
    };
  }, [url]);

  const emit = useCallback((event: string, data: unknown) => {
    socketRef.current?.emit(event, data);
  }, []);

  const on = useCallback((event: string, handler: (data: unknown) => void) => {
    socketRef.current?.on(event, handler);
    return () => {
      socketRef.current?.off(event, handler);
    };
  }, []);

  return { isConnected, emit, on, socket: socketRef.current };
}

TanStack Query Integration

import { useQueryClient } from '@tanstack/react-query';

function useRealtimeUpdates() {
  const queryClient = useQueryClient();

  useWebSocket({
    url: 'wss://api.example.com/ws',
    onMessage: (data: any) => {
      switch (data.type) {
        case 'user_updated':
          queryClient.invalidateQueries({ queryKey: ['users', data.userId] });
          break;
        case 'message_created':
          queryClient.setQueryData(['messages', data.roomId], (old: Message[]) => [
            ...old,
            data.message,
          ]);
          break;
      }
    },
  });
}

Connection Status Component

function ConnectionStatus() {
  const { isConnected } = useWS();

  return (
    <div
      className={`flex items-center gap-2 ${isConnected ? 'text-green-500' : 'text-red-500'}`}
    >
      <span className={`h-2 w-2 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'}`} />
      {isConnected ? 'Connected' : 'Disconnected'}
    </div>
  );
}

Anti-Patterns

Anti-PatternWhy BadCorrect Approach
Creating WebSocket in renderCreates new connection every renderUse useRef or context
Not cleaning up on unmountMemory leaks, duplicate connectionsReturn cleanup in useEffect
Missing reconnection logicLost connections not recoveredImplement exponential backoff
Storing socket in stateUnnecessary re-rendersUse useRef instead
Not handling connection errorsSilent failuresAdd onError handler

Quick Troubleshooting

SymptomCauseFix
Connection closes immediatelyCORS or auth issuesCheck server CORS config
Messages not receivedWrong event typeVerify message format matches
Multiple connectionsMissing cleanupAdd cleanup in useEffect
State not updatingStale closureUse functional setState
Reconnect loopServer rejectingAdd max reconnect attempts

Production Checklist

  • Reconnection with exponential backoff
  • Connection status indicator
  • Graceful disconnect on unmount
  • Error handling and logging
  • Authentication token refresh
  • Message queue for offline
  • Heartbeat/ping mechanism

When NOT to Use This Skill

  • For server-side WebSocket (NestJS) → use nestjs-websocket
  • For Spring WebSocket → use spring-websocket
  • For simple HTTP polling → use tanstack-query with refetchInterval

Reference Documentation

发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

2026年9月24日

分类

未分类

许可证

MIT

源路径

skills/frontend-frameworks/react-websocket

默认分支

main

最新提交

9496306

Tree SHA

fe4e2f1