react-use-socket
v0.1.2
Published
The package which makes web socket management much easier by using hooks
Downloads
77
Maintainers
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
- One socket example
- Multiple sockets example
- Provider options
- Hooks usage
- Custom Error Type
- Provider options declaration
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.