npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-use-socket

v0.1.2

Published

The package which makes web socket management much easier by using hooks

Downloads

77

Readme

React Use Socket

The package which makes web socket management much easier by using hooks. The package is built over the WebSocket constructor from browser API.


Structure


Installation

# using npm
npm install react-use-socket

# using yarn
yarn add react-use-socket

Provider options

WebSocketOptions

| Name | Required | Type | Default | | ------------------------------------------------- | -------- | --------------------------------------------------- | ----------- | | url | Yes | string | - | | getRequestIndicator | Yes | (req: Req) => string OR number | - | | getResponseIndicator | Yes | (res: Res) => string OR number | - | | getError | Yes | (res: Res) => string OR Err OR null | - | | autoConnect | No | boolean | true | | protocols | No | string OR string[] | - | | shouldReconnect | No | ((event: CloseEvent) => boolean) OR boolean | true | | reconnectionIntervals | No | number OR number[] | 1000 | | serialize | No | (req: Req) => SReq | - | | deserialize | No | (res: Res) => DRes | - | | debug | No | boolean | - |


url (required)

string

Url for the WebSocket constructor.

url: 'ws://localhost:3000'
url: 'wss://example.com'

getRequestIndicator (required)

(res: Res) => string | number

WARNING: Make sure that the getRequestIndicator(req) value indicator is exactly same as getResponseIndicator(res). The package needs to know which the request received response belongs to.

Let us say that the request which needs to be sent to the API looks as:

(this is just an example it's not a requirement to the API request type)

req = {
  get_user: {
    id: 1
  }
}

The indicator is get_user so the prop should be:

getRequestIndicator: req => Object.keys(req)[0]

getResponseIndicator (required)

(res: Res) => string | number

WARNING: Make sure that the getResponseIndicator(res) value indicator is exactly same as getRequestIndicator(req). The package needs to know which the request received response belongs to.

Let us say that the response which comes from the API and needs to be handled looks as:

(this is just an example it's not a requirement to the API response type)

res = {
  get_user: {
    id: 1,
    username: '@...',
    avatarUrl: 'https://...'
  }
}

The indicator is get_user so the prop should be:

getResponseIndicator: req => Object.keys(req)[0]

getError (required)

(res: Res) => string | Err | null

Let us say that the failure response which comes from the API looks as:

(this is just an example it's not a requirement to the API response type)

res = {
  get_user: {
    error: 'Not found'
  }
}

The error is Not found so the prop should be:

getError: res => res[Object.keys(req)[0]].error || null

When using custom error type Err: (see doc of the [Custom error type](#Custom error type))

res = {
  get_user: {
    error: {
      message: 'Not found',
      meta: {...}
    }
  }
}

The error is an object so the prop should be:

getError: res => res[Object.keys(req)[0]].error || null

autoConnect

boolean - (true by default)

When true you don't need to send anything to connect it. When false you need to connect the socket manually by using useWebSocketState hook.

autoConnect: true

shouldReconnect

((event: CloseEvent) => boolean) | boolean - (true by default)

When true the socket tries to reconnect if event.code !== 1005. When the predicate is passed you are able to decide if the socket needs to be reconnected.

shouldReconnect: true

debug

boolean

When true the package shows additional logs.

debug: ture

protocols

boolean

Protocols for the WebSocket constructor.

protocols: 'some protocol'
protocols: ['some protocol']

reconnectionInterval

number | number[] - (1000 by default)

In milliseconds. When array each new connection uses the next number from the array for a timeout to avoid DDOSing a server.

reconnectionInterval: 1000

When reconnection count reaches the last array element it uses it each the next time. When the socket connects back the next reconnection loop will start from the 0 index.

reconnectionInterval: [0, 1000, 2000, 3000, 4000, 5000, 10000]

serialize

(req: Req) => SReq Req and SReq are templates of the generic MiddlewareOptions type

The format function gets called to prepare the data to get submitted to the server. For example, camelCase to snake_case conversion.

serialize: req => {
  return {
    ...req,
    time: Date.now()
  }
}

deserialize

(res: Res) => DRes Res and DRes are templates of the generic MiddlewareOptions type

The format function gets called to prepare the message to get submitted to the onMessage callback. For example, snake_case to camelCase conversion.

deserialize: res => {
  return res.data
}

Custom Error Type

enum Socket {
  MAIN = 'Main'
}

type Req = {
  get_user: {
    id: number
  }
}

type DRes = {
  get_user: {
    username: string
    avatarUrl: string
  }
}

type Error = {
  message: string
  meta: {
    timestamp: number
    service: string
    ...
  }
}

Putting these types into a generic hook:

const signalData = useSignal<Req, Res, Socket, Error>({...})
const [signalData, signalControls] = useLazySignal<Req, Res, Socket, Error>()
const [subscriptionData, subscriptionControls] = useSubscription<DRes, Socket, Error>('')
const [subscriptionData, subscriptionControls] = useLazySubscription<DRes, Socket, Error>('')

Hooks usage

useWebSocketState

When you use only one socket, passing the socket name is optional.

import React from 'react';
import { useWebSocketState } from 'react-awesome-websocket';

const Component = () => {
  const [connected, { open, close }] = useWebSocketState();

  return (
    <>
      <h1>useWebSocketState example</h1>
      <h2>Connected: {connected}</h2>
      <button onClick={open} disabled={connected}>Open</button>
      <button onClick={close} disabled={!connected}>Close</button>
    <>
  );
};

When you use multiple sockets, passing the socket name is required. Otherwise, you get the The "name" is required for the hook usage error.

import React from 'react';
import { useWebSocketState } from 'react-awesome-websocket';

enum Socket {
  MAIN = 'Main'
}

const Component = () => {
  const [connected, { open, close }] = useWebSocketState<Socket>({ name: Socket.MAIN });

  return (
    <>
      <h1>useWebSocketState example</h1>
      <h2>Connected: {connected}</h2>
      <button onClick={open} disabled={connected}>Open</button>
      <button onClick={close} disabled={!connected}>Close</button>
    <>
  );
};

useSignal

When you use only one socket, passing the socket name is optional.

import React from 'react';
import { useSignal } from 'react-awesome-websocket';

type Req = {
  get_user: {
    id: number
  }
}

type Res = {
  get_user: {
    username: string
    avatarUrl: string
  }
}

const Component = () => {
  const { loading, error, data, mounted } = useSignal<Req, Res>({
    get_user: { id: 1 }
  });

  return (
    <>
      <h1>useSignal example</h1>
      <h2>Loading: {loading}</h2>
      <h2>Error: {error}</h2>
      <h2>Mounted: {mounted}</h2>
      <h2>Data:</h2>
      <pre>{JSON.stringify(data, null, 4)}</pre>
    <>
  );
};

When you use multiple sockets, passing the socket name is required. Otherwise, you get the The "name" is required for the hook usage error.

import React from 'react';
import { useSignal } from 'react-awesome-websocket';

enum Socket {
  MAIN = 'Main'
}

type Req = {
  get_user: {
    id: number
  }
}

type Res = {
  get_user: {
    username: string
    avatarUrl: string
  }
}

const Component = () => {
  const { loading, error, data, mounted } = useSignal<Req, Res, Socket>({
    get_user: { id: 1 }
  }, { name: Socket.MAIN });

  return (
    <>
      <h1>useSignal example</h1>
      <h2>Loading: {loading}</h2>
      <h2>Error: {error}</h2>
      <h2>Mounted: {mounted}</h2>
      <h2>Data:</h2>
      <pre>{JSON.stringify(data, null, 4)}</pre>
    <>
  );
};

useLazySignal

When you use only one socket, passing the socket name is optional.

import React from 'react';
import { useLazySignal } from 'react-awesome-websocket';

type Req = {
  get_user: {
    id: number
  }
}

type DRes = {
  get_user: {
    username: string
    avatarUrl: string
  }
}

const Component = () => {
  const [signalData, { send }] = useLazySignal<Req, DRes>({
    get_user: { id: 1 }
  });

  const { loading, error, data, mounted } = signalData;
  
  const handleSendClick = () => {
    send({ get_user: { id: 1 } });
  }

  return (
    <>
      <h1>useLazySignal example</h1>
      <button onClick={handleSendClick}>Send Request</button>
      <h2>Loading: {loading}</h2>
      <h2>Error:</h2>
      <pre>{JSON.stringify(error, null, 4)}</pre>
      <h2>Mounted: {mounted}</h2>
      <h2>Data:</h2>
      <pre>{JSON.stringify(data, null, 4)}</pre>
    <>
  );
};

When you use multiple sockets, passing the socket name is required. Otherwise, you get the The "name" is required for the hook usage error.

import React from 'react';
import { useLazySignal } from 'react-awesome-websocket';

enum Socket {
  MAIN = 'Main'
}

type Req = {
  get_user: {
    id: number
  }
}

type DRes = {
  get_user: {
    username: string
    avatarUrl: string
  }
}

const Component = () => {
  const [signalData, { send }] = useLazySignal<Req, DRes, Socket>({
    get_user: { id: 1 }
  }, { name: Socket.MAIN });

  const { loading, error, data, mounted } = signalData;
  
  const handleSendClick = () => {
    send({ get_user: { id: 1 } });
  }

  return (
    <>
      <h1>useLazySignal example</h1>
      <button onClick={handleSendClick}>Send Request</button>
      <h2>Loading: {loading}</h2>
      <h2>Error:</h2>
      <pre>{JSON.stringify(error, null, 4)}</pre>
      <h2>Mounted: {mounted}</h2>
      <h2>Data:</h2>
      <pre>{JSON.stringify(data, null, 4)}</pre>
    <>
  );
};

useSubscription

When you use only one socket, passing the socket name is optional.

import React from 'react';
import { useSubscription } from 'react-awesome-websocket';

type DRes = {
  user_update: {
    username: string
    avatarUrl: string
  }
}

const Component = () => {
  const [{ data, error }, { stop }] = useSubscription<DRes>('user_update');

  return (
    <>
      <h1>useSubscription example</h1>
      <button onClick={stop}>Stop subscription</button>
      <h2>Error: {error}</h2>
      <h2>Data:</h2>
      <pre>{JSON.stringify(data, null, 4)}</pre>
    <>
  );
};

When you use multiple sockets, passing the socket name is required. Otherwise, you get the The "name" is required for the hook usage error.

import React from 'react';
import { useSubscription } from 'react-awesome-websocket';

enum Socket {
  MAIN = 'Main'
}

type DRes = {
  user_update: {
    username: string
    avatarUrl: string
  }
}

const Component = () => {
  const [{ data, error }, { stop }] = useSubscription<DRes, Socket>('user_update', {
    name: Socket.MAIN
  });
  return (
    <>
      <h1>useSubscription example</h1>
      <button onClick={stop}>Stop subscription</button>
      <h2>Error: {error}</h2>
      <h2>Data:</h2>
      <pre>{JSON.stringify(data, null, 4)}</pre>
    <>
  );
};

useLazySubscription

When you use only one socket, passing the socket name is optional.

import React from 'react';
import { useLazySubscription } from 'react-awesome-websocket';

type Req = {
  get_user: {
    id: number
  }
}

type DRes = {
  get_user: {
    username: string
    avatarUrl: string
  }
}

const Component = () => {
  const [{ data, error }, { start, stop }] = useLazySubscription<DRes>('user_update');

  return (
    <>
      <h1>useLazySubscription example</h1>
      <button onClick={start}>Start subscription</button>
      <button onClick={stop}>Stop subscription</button>
      <h2>Error: {error}</h2>
      <h2>Data:</h2>
      <pre>{JSON.stringify(data, null, 4)}</pre>
    <>
  );
};

When you use multiple sockets, passing the socket name is required. Otherwise, you get the The "name" is required for the hook usage error.

import React from 'react';
import { useLazySubscription } from 'react-awesome-websocket';

enum Socket {
  MAIN = 'Main'
}

type Req = {
  get_user: {
    id: number
  }
}

type DRes = {
  get_user: {
    username: string
    avatarUrl: string
  }
}

const Component = () => {
  const [{ data, error }, { start, stop }] = useLazySubscription<DRes, Socket>('user_update', {
    name: Socket.MAIN
  });

  return (
    <>
      <h1>useLazySubscription example</h1>
      <button onClick={start}>Start subscription</button>
      <button onClick={stop}>Stop subscription</button>
      <h2>Error: {error}</h2>
      <h2>Data:</h2>
      <pre>{JSON.stringify(data, null, 4)}</pre>
    <>
  );
};

Provider options declaration

import { WebSocketOptions } from 'react-awesome-websocket';

enum Scoket {
  MAIN = 'Main'
};

type ScoketReq = {
  method: string
  data: Record<string, unknown>
};

type SocketRes = {
  [method: string]: Record<string, unknown>
};

type ScoketSerializedReq = {
  [method: string]: Record<string, unknown>
};

type SocketDeserializedRes = Record<string, unknown>;

type SocketError = {
  message: string
  meta: {}
};

const options: WebSocketOptions<
  ScoketReq,
  SocketRes,
  Scoket,
  SocketError,
  ScoketSerializedReq,
  SocketDeserializedRes
> = {
  [Socket.MAIN]: {
    url: 'ws://localhost:3000',
    getRequestIndicator: req => req.method,
    getResponseIndicator: res => Object.keys(res)[0],
    getError: res => res[Object.keys(res)[0]].error_msg || null,

    // serialize: (req: ScoketReq) => ScoketSerializedReq
    serialize: ({ method, data }) => ({ [method]: data }),

    // deserialize: (res: SocketRes) => SocketDeserializedRes
    deserialize: (res: SocketRes) => res[Object.keys(res)[0]]
  }
};

Passing own types to WebSocketOptions type

WebSocketOptions is a generic type.

WebSocketOptions<Req, Res, N extends string = stirng, Err = string, SReq = Req, DRes = Res>

Req - type of the socket request (required).

Res - type of the socket response (required).

N (default is string) - type of the sockets' names. This type should be passed into every hook if you need to use multiple sockets.

Err (default is string) - type of the socket error which is reachable by using hooks as error (not required).

SReq (default is Req) - type of serialized socket request which will be sent to the API (not required). This type should be returned from the WebSocketOptions.serialize function.

DRes (default is Res) - type of deserialized socket response which is reachable by using hooks as data (not required). This type should be returned from the WebSocketOptions.deserialize function.