react-ws-context 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +105 -82
- package/README.zh-TW.md +93 -83
- package/dist/index.d.mts +30 -29
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +71 -65
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.1] - 2026-08-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Export `LivenessOptions` from the package entry
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- README (EN / zh-TW): align structure and wording; fix immutable-config URL guidance; clarify render isolation, stall parsing, and `WsProvider` lifecycle
|
|
19
|
+
- JSDoc (`CreateWsContextOptions`, `WsContextValue`): clearer field descriptions; fix `autoConnect` wording
|
|
20
|
+
|
|
21
|
+
## [0.4.0] - 2026-08-29
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Store `setState` skips listener notification when partial values are unchanged (shallow compare on updated keys)
|
|
26
|
+
- Provider unmount syncs store to `status: "closed"` and `phase: "idle"` (consistent with `disconnect()`)
|
|
27
|
+
- Liveness ping and timeout both use `getActiveSocket()` for the active socket
|
|
28
|
+
- `sendJson` returns `false` when `JSON.stringify` fails (e.g. circular reference) instead of throwing
|
|
29
|
+
- `useWsEvents` handler ref sync moved into `useEffect` (complies with `react-hooks/refs`)
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Shared `teardown()` for `disconnect()` and provider unmount cleanup
|
|
34
|
+
- On unintentional close, store updates before `close` is emitted so handlers see consistent `status` / `phase`
|
|
35
|
+
- Batch `status` and `phase` store updates where both change together
|
|
36
|
+
- `Liveness.start()` takes no socket argument; uses `getActiveSocket()` at runtime
|
|
37
|
+
- Internal layout: `useWsEventsApi` in `ws-events.ts` (`emitter.ts` is React-free); aligned Context module structure (`ws-store`, `ws-events`, `ws-actions`)
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- Store smoke test for `setState` deduplication
|
|
42
|
+
|
|
10
43
|
## [0.3.0] - 2026-08-29
|
|
11
44
|
|
|
12
45
|
### Added
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
> **繁體中文:** [README.zh-TW.md](./README.zh-TW.md)
|
|
7
7
|
|
|
8
|
-
A React **WebSocket connection
|
|
8
|
+
A React **WebSocket connection-layer** package. It separates connection lifecycle, subscribable state, and message events so connection status or high-frequency messages do not re-render your entire component tree.
|
|
9
9
|
|
|
10
10
|
> **Maintainer:** [GaiaYang](https://github.com/GaiaYang)
|
|
11
11
|
> **Source:** [github.com/GaiaYang/react-ws](https://github.com/GaiaYang/react-ws) (package path: `packages/react-ws`)
|
|
@@ -14,7 +14,7 @@ A React **WebSocket connection layer**. It separates connection lifecycle, subsc
|
|
|
14
14
|
|
|
15
15
|
- **Zero runtime dependencies** — only `react >= 18` as a peer dependency
|
|
16
16
|
- **Frozen config** — `url`, `reconnectMs`, etc. are fixed at `createWsContext`; use `connect` / `disconnect` at runtime
|
|
17
|
-
- **Render isolation** — connection-layer state (health / queue / reconnect) lives in an external store; messages
|
|
17
|
+
- **Render isolation** — connection-layer state (health / queue / reconnect) lives in an external store; messages are delivered through an event emitter and subscribed via `useWsEvents` (not written into React state)
|
|
18
18
|
- **Optional liveness** — periodic ping / pong; closes the socket on timeout to trigger reconnect
|
|
19
19
|
- **Optional outbound queue** — buffers messages while not OPEN, flushes on connect
|
|
20
20
|
|
|
@@ -87,12 +87,13 @@ function Chat() {
|
|
|
87
87
|
createWsContext(options)
|
|
88
88
|
│
|
|
89
89
|
├── WsProvider WebSocket instance, reconnect, liveness, outbound queue
|
|
90
|
-
├── useWsActions() send / connect / disconnect — no re-renders
|
|
90
|
+
├── useWsActions() send / connect / disconnect / getStatus — no re-renders
|
|
91
91
|
├── useWsStore() connection-layer state: health / queue / reconnect
|
|
92
92
|
└── useWsEvents() open / message / error / close
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
- **Call `createWsContext` multiple times** for independent connections (e.g. app WS + notification WS).
|
|
96
|
+
- **Provider internals** — each `WsProvider` owns a separate store and event emitter (not public API); actions are assembled in `WsProvider` via `useMemo` and passed through Context.
|
|
96
97
|
- **`WsState` holds low-frequency connection data only** — health (`status`, `phase`), outbound queue (e.g. future `pendingCount`), reconnect (`reconnectAttempt`). **Not** message payloads or app data.
|
|
97
98
|
- **Messages and errors** — use `useWsEvents`; keep message history in your own state, cache, or store.
|
|
98
99
|
- **Connection errors are not a `WsStatus`** — use `useWsEvents("error")`; native `error` is usually followed by `close`.
|
|
@@ -107,16 +108,16 @@ Creates a `WsProvider` and hooks bound to the same connection config.
|
|
|
107
108
|
|
|
108
109
|
#### `CreateWsContextOptions`
|
|
109
110
|
|
|
110
|
-
| Field | Type | Default | Description
|
|
111
|
-
| ------------------ | ----------------------------------------- | ---------- |
|
|
112
|
-
| `url` | `string` | (required) | WebSocket URL
|
|
113
|
-
| `protocols` | `string \| string[]` | — | Passed to `new WebSocket(url, protocols)`
|
|
114
|
-
| `autoConnect` | `boolean` | `true` |
|
|
115
|
-
| `reconnectMs` | `number` | `0` | Reconnect delay (ms) after unintentional close; `0` disables reconnect
|
|
116
|
-
| `reconnectMax` | `number` | `0` | Max auto-reconnects after unintentional close
|
|
117
|
-
| `outgoingQueueMax` | `number` | `0` | Max outbound queue size while not OPEN; `0` disables the queue
|
|
118
|
-
| `parse` | `(data: MessageEvent["data"]) => unknown` | see below | Transform raw `MessageEvent.data`
|
|
119
|
-
| `liveness` | `LivenessOptions` | — | Liveness / heartbeat config; omit to disable
|
|
111
|
+
| Field | Type | Default | Description |
|
|
112
|
+
| ------------------ | ----------------------------------------- | ---------- | ----------------------------------------------------------------------------------------- |
|
|
113
|
+
| `url` | `string` | (required) | WebSocket URL |
|
|
114
|
+
| `protocols` | `string \| string[]` | — | Passed to `new WebSocket(url, protocols)` |
|
|
115
|
+
| `autoConnect` | `boolean` | `true` | Auto-connect when `WsProvider` loads |
|
|
116
|
+
| `reconnectMs` | `number` | `0` | Reconnect delay (ms) after unintentional close; `0` disables reconnect |
|
|
117
|
+
| `reconnectMax` | `number` | `0` | Max auto-reconnects after unintentional close; `0` unlimited (requires `reconnectMs > 0`) |
|
|
118
|
+
| `outgoingQueueMax` | `number` | `0` | Max outbound queue size while not OPEN; `0` disables the queue |
|
|
119
|
+
| `parse` | `(data: MessageEvent["data"]) => unknown` | see below | Transform raw `MessageEvent.data` |
|
|
120
|
+
| `liveness` | `LivenessOptions` | — | Liveness / heartbeat config; omit to disable |
|
|
120
121
|
|
|
121
122
|
**Default `parse`:**
|
|
122
123
|
|
|
@@ -138,12 +139,13 @@ Creates a `WsProvider` and hooks bound to the same connection config.
|
|
|
138
139
|
|
|
139
140
|
Creates, owns, and tears down the native `WebSocket`.
|
|
140
141
|
|
|
141
|
-
| Behavior
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
| unmount
|
|
145
|
-
|
|
|
146
|
-
|
|
|
142
|
+
| Behavior | Description |
|
|
143
|
+
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
144
|
+
| `WsProvider` loads + `autoConnect: true` | Auto-connects |
|
|
145
|
+
| unmount | Cancels reconnect (`reconnectAttempt` and `reconnectExhausted` reset), stops liveness, clears outbound queue; syncs store to `status: "closed"`, `phase: "idle"`; closes socket and emits `close` (reason: `"provider unmount"`) |
|
|
146
|
+
| `disconnect()` | Same cleanup and store reset as unmount, no auto-reconnect; emits `close` (reason: `"client disconnect"`) |
|
|
147
|
+
| reconnect | Fixed interval when `reconnectMs > 0` and close was not intentional (no exponential backoff); stops after `reconnectMax` if `> 0` |
|
|
148
|
+
| `connect()` with existing socket | Closes the previous socket and emits `close` (reason: `"reconnect"`) before opening a new one (manual connect or auto-reconnect) |
|
|
147
149
|
|
|
148
150
|
---
|
|
149
151
|
|
|
@@ -151,27 +153,29 @@ Creates, owns, and tears down the native `WebSocket`.
|
|
|
151
153
|
|
|
152
154
|
Must be used inside the matching `WsProvider`. Return value is memoized and **does not** re-render on store or message updates.
|
|
153
155
|
|
|
154
|
-
| Method | Signature | Description
|
|
155
|
-
| ------------ | ---------------------------- |
|
|
156
|
-
| `send` | `(data) => boolean` | Send raw data. Sends immediately when OPEN; otherwise enqueues if configured |
|
|
157
|
-
| `sendJson` | `(data: unknown) => boolean` | `JSON.stringify` then `send`
|
|
158
|
-
| `connect` | `() => void` | Open connection; closes any existing socket first
|
|
159
|
-
| `disconnect` | `() => void` | Intentional close; no auto-reconnect; clears outbound queue
|
|
160
|
-
| `getStatus` | `() => WsStatus` | Read current status; no subscription, no re-render
|
|
156
|
+
| Method | Signature | Description |
|
|
157
|
+
| ------------ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
158
|
+
| `send` | `(data) => boolean` | Send raw data (`string`, `ArrayBuffer`, `Blob`, etc.). Sends immediately when OPEN; otherwise enqueues if configured |
|
|
159
|
+
| `sendJson` | `(data: unknown) => boolean` | `JSON.stringify` then `send`; same return semantics as `send`; `false` if not serializable |
|
|
160
|
+
| `connect` | `() => void` | Open connection; closes any existing socket first (see `WsProvider`) |
|
|
161
|
+
| `disconnect` | `() => void` | Intentional close; sets store to `phase: "idle"`, `status: "closed"`; no auto-reconnect; clears outbound queue |
|
|
162
|
+
| `getStatus` | `() => WsStatus` | Read current status; no subscription, no re-render |
|
|
161
163
|
|
|
162
164
|
**`send` / `sendJson` return value:**
|
|
163
165
|
|
|
164
166
|
- `true` — sent or enqueued
|
|
165
|
-
- `false` — not
|
|
167
|
+
- `false` — not sent: queue full, queue disabled, or `sendJson` could not serialize
|
|
166
168
|
|
|
167
169
|
---
|
|
168
170
|
|
|
169
171
|
### `useWsStore()`
|
|
170
172
|
|
|
171
|
-
Must be used inside the matching `WsProvider`. Uses `useSyncExternalStore` under the hood.
|
|
173
|
+
Must be used inside the matching `WsProvider`. Uses `useSyncExternalStore` under the hood; **partial updates with unchanged values do not notify subscribers** (shallow compare).
|
|
172
174
|
|
|
173
175
|
`WsState` is for **connection health / outbound queue / reconnect** — low-frequency lifecycle data. For high-frequency messages, use `useWsEvents("message", …)`, not the store.
|
|
174
176
|
|
|
177
|
+
**Tip:** use a selector to subscribe only to the fields you need (e.g. `(s) => s.phase`). `useWsStore()` without a selector subscribes to the full state — any field change triggers a re-render.
|
|
178
|
+
|
|
175
179
|
```ts
|
|
176
180
|
useWsStore(): WsState
|
|
177
181
|
useWsStore<T>(selector: (state: WsState) => T): T
|
|
@@ -193,9 +197,9 @@ interface WsState {
|
|
|
193
197
|
}
|
|
194
198
|
```
|
|
195
199
|
|
|
196
|
-
| Belongs in store
|
|
197
|
-
|
|
|
198
|
-
| `status`, `phase`, reconnect progress (`reconnectAttempt` / `reconnectExhausted`),
|
|
200
|
+
| Belongs in store | Does not belong |
|
|
201
|
+
| ------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
|
|
202
|
+
| `status`, `phase`, reconnect progress (`reconnectAttempt` / `reconnectExhausted`), liveness / stall summaries | `lastMessage`, message history, app payloads |
|
|
199
203
|
|
|
200
204
|
`CreateWsContextOptions` (e.g. `url`, `reconnectMax`) are frozen at `createWsContext` and are **not** in `WsState`. For UI like `n/max`, keep the config alongside the store fields you subscribe to.
|
|
201
205
|
|
|
@@ -212,17 +216,17 @@ Maps to the current WebSocket connection state (similar to readyState). Does **n
|
|
|
212
216
|
|
|
213
217
|
#### `WsPhase`
|
|
214
218
|
|
|
215
|
-
| Value
|
|
216
|
-
|
|
|
217
|
-
| `idle`
|
|
218
|
-
| `connecting`
|
|
219
|
-
| `open`
|
|
220
|
-
| `reconnecting`
|
|
221
|
-
| `stopped`
|
|
219
|
+
| Value | Meaning |
|
|
220
|
+
| -------------- | -------------------------------------------------------------------------------------------------- |
|
|
221
|
+
| `idle` | Not connected, no reconnect scheduled (initial or manual `disconnect()`) |
|
|
222
|
+
| `connecting` | First connect or manual `connect()` in progress |
|
|
223
|
+
| `open` | Connected |
|
|
224
|
+
| `reconnecting` | Auto-reconnect cycle (waiting for timer or connecting); pair with `status`, `reconnectAttempt` |
|
|
225
|
+
| `stopped` | Will not auto-reconnect; use `reconnectExhausted` to distinguish max retries vs reconnect disabled |
|
|
222
226
|
|
|
223
227
|
`status` and `phase` often change together but mean different things. For example, `phase === "reconnecting"` with `status === "closed"` means waiting for the reconnect timer; `status === "connecting"` means the timer fired and a connect attempt is in progress.
|
|
224
228
|
|
|
225
|
-
**
|
|
229
|
+
**Example:**
|
|
226
230
|
|
|
227
231
|
```tsx
|
|
228
232
|
const phase = useWsStore((s) => s.phase);
|
|
@@ -252,20 +256,24 @@ Must be used inside the matching `WsProvider`. Registers in `useEffect` and unsu
|
|
|
252
256
|
|
|
253
257
|
- Handler is kept in a ref — changing the callback does **not** re-subscribe
|
|
254
258
|
- Changing `type` **does** re-subscribe
|
|
259
|
+
- On unintentional close, the store is updated to `status: "closed"` and the appropriate `phase` before the `close` handler runs
|
|
260
|
+
- Intentional `disconnect()` or provider unmount follows the same order: store first, then `close`
|
|
255
261
|
- For multiple events, call `useWsEvents` multiple times
|
|
256
262
|
|
|
257
263
|
---
|
|
258
264
|
|
|
259
|
-
### Liveness
|
|
265
|
+
### Liveness
|
|
266
|
+
|
|
267
|
+
Enable via `createWsContext({ liveness: { … } })`. After OPEN, sends periodic application-layer pings (JSON via `send`, not WebSocket control frames); if no matching pong within `timeoutMs`, closes the socket (which can trigger reconnect).
|
|
260
268
|
|
|
261
|
-
|
|
269
|
+
Shape of the `liveness` option:
|
|
262
270
|
|
|
263
271
|
```ts
|
|
264
272
|
interface LivenessOptions {
|
|
265
|
-
intervalMs: number;
|
|
266
|
-
timeoutMs: number;
|
|
267
|
-
ping: unknown | (() => unknown);
|
|
268
|
-
isPong: (data: unknown) => boolean;
|
|
273
|
+
intervalMs: number; // ping interval (ms)
|
|
274
|
+
timeoutMs: number; // wait for pong (ms)
|
|
275
|
+
ping: unknown | (() => unknown); // ping payload; function for dynamic values
|
|
276
|
+
isPong: (data: unknown) => boolean; // whether parsed data is a pong
|
|
269
277
|
}
|
|
270
278
|
```
|
|
271
279
|
|
|
@@ -287,7 +295,7 @@ createWsContext({
|
|
|
287
295
|
});
|
|
288
296
|
```
|
|
289
297
|
|
|
290
|
-
Every incoming message is checked with `isPong`; a pong
|
|
298
|
+
Every incoming message is checked with `isPong`; a pong clears the timeout timer and still emits `"message"`. Ping payloads are always sent via `JSON.stringify` (JSON only).
|
|
291
299
|
|
|
292
300
|
---
|
|
293
301
|
|
|
@@ -295,14 +303,14 @@ Every incoming message is checked with `isPong`; a pong resets the timeout timer
|
|
|
295
303
|
|
|
296
304
|
When `outgoingQueueMax > 0`:
|
|
297
305
|
|
|
298
|
-
| When | Behavior
|
|
299
|
-
| -------------------------- |
|
|
300
|
-
| `send` while not OPEN | Enqueue (FIFO)
|
|
301
|
-
| Queue full | Returns `false`; does **not** drop older messages
|
|
302
|
-
| Socket OPEN | Flush entire queue in order
|
|
303
|
-
| `disconnect()` | Clear queue
|
|
304
|
-
| `WsProvider` unmount | Clear queue
|
|
305
|
-
| Waiting for auto-reconnect | **Keep** queue
|
|
306
|
+
| When | Behavior |
|
|
307
|
+
| -------------------------- | -------------------------------------------------------------- |
|
|
308
|
+
| `send` while not OPEN | Enqueue (FIFO) |
|
|
309
|
+
| Queue full | Returns `false`; does **not** drop older messages |
|
|
310
|
+
| Socket OPEN | Flush entire queue in order |
|
|
311
|
+
| `disconnect()` | Clear queue; store set to `idle` / `closed` (see `WsProvider`) |
|
|
312
|
+
| `WsProvider` unmount | Clear queue; store synced (see `WsProvider`) |
|
|
313
|
+
| Waiting for auto-reconnect | **Keep** queue |
|
|
306
314
|
|
|
307
315
|
---
|
|
308
316
|
|
|
@@ -310,14 +318,15 @@ When `outgoingQueueMax > 0`:
|
|
|
310
318
|
|
|
311
319
|
From the main `react-ws-context` entry:
|
|
312
320
|
|
|
313
|
-
| Type | Description
|
|
314
|
-
| ------------------------ |
|
|
315
|
-
| `CreateWsContextOptions` | Options for `createWsContext`
|
|
316
|
-
| `
|
|
317
|
-
| `
|
|
318
|
-
| `
|
|
321
|
+
| Type | Description |
|
|
322
|
+
| ------------------------ | -------------------------------------------------------- |
|
|
323
|
+
| `CreateWsContextOptions` | Options for `createWsContext` |
|
|
324
|
+
| `LivenessOptions` | Options for `liveness` in `createWsContext` |
|
|
325
|
+
| `WsContextValue` | Return type of `useWsActions()` |
|
|
326
|
+
| `WsEvents` | Event name → handler map |
|
|
327
|
+
| `WsStatus` | WebSocket connection state (`WsState`) |
|
|
319
328
|
| `WsPhase` | Provider connection intent / reconnect phase (`WsState`) |
|
|
320
|
-
| `WsState` | Subscribable store shape (health / queue / reconnect)
|
|
329
|
+
| `WsState` | Subscribable store shape (health / queue / reconnect) |
|
|
321
330
|
|
|
322
331
|
---
|
|
323
332
|
|
|
@@ -337,28 +346,42 @@ import {
|
|
|
337
346
|
} from "react-ws-context/stall";
|
|
338
347
|
```
|
|
339
348
|
|
|
340
|
-
| Export | Description
|
|
341
|
-
| ---------------------------- |
|
|
342
|
-
| `STALL_MESSAGE_TYPE` | Client control message type (`"STALL"`)
|
|
343
|
-
| `STALL_ACK_TYPE` | Server ack type (`"STALL_ACK"`)
|
|
344
|
-
| `createStallMessage(action)` | Build a message for `sendJson`
|
|
345
|
-
| `parseStallMessage(data)` | Parse from `useWsEvents("message")` data; `null` if invalid
|
|
346
|
-
| `StallAction` | `"stall" \| "release"`
|
|
347
|
-
| `StallMessage` | `{ type: "STALL"; action: StallAction }`
|
|
348
|
-
| `StallAck` | `{ type: "STALL_ACK"; action: StallAction; active: boolean }` |
|
|
349
|
+
| Export | Description |
|
|
350
|
+
| ---------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
351
|
+
| `STALL_MESSAGE_TYPE` | Client control message type (`"STALL"`) |
|
|
352
|
+
| `STALL_ACK_TYPE` | Server ack type (`"STALL_ACK"`) — for typing only; no built-in parser |
|
|
353
|
+
| `createStallMessage(action)` | Build a client `STALL` message for `sendJson` |
|
|
354
|
+
| `parseStallMessage(data)` | Parse client `STALL` messages from `useWsEvents("message")` data; `null` if invalid (not `STALL_ACK`) |
|
|
355
|
+
| `StallAction` | `"stall" \| "release"` |
|
|
356
|
+
| `StallMessage` | `{ type: "STALL"; action: StallAction }` |
|
|
357
|
+
| `StallAck` | `{ type: "STALL_ACK"; action: StallAction; active: boolean }` — parse server acks yourself |
|
|
358
|
+
|
|
359
|
+
**Example:**
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
const { sendJson } = useWsActions();
|
|
363
|
+
|
|
364
|
+
useWsEvents("message", (data) => {
|
|
365
|
+
const stall = parseStallMessage(data);
|
|
366
|
+
if (stall) console.log("stall control", stall.action);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
sendJson(createStallMessage("stall"));
|
|
370
|
+
```
|
|
349
371
|
|
|
350
372
|
---
|
|
351
373
|
|
|
352
374
|
## Design trade-offs
|
|
353
375
|
|
|
354
|
-
| Topic | Notes
|
|
355
|
-
| ---------------- |
|
|
356
|
-
| Immutable config | `url`, `reconnectMs`, etc. are fixed at create time
|
|
357
|
-
| Reconnect | Fixed interval only; no exponential backoff; optional cap via `reconnectMax`
|
|
358
|
-
| SSR | No `WebSocket` on the server; `connect()` is a no-op without `window`
|
|
359
|
-
| Error status | No `"error"` in `WsStatus`; use `useWsEvents("error")`
|
|
360
|
-
| `WsState` scope | Health / queue / reconnect only — not messages or app data
|
|
361
|
-
| Rendering | Components that only call `useWsActions` do not re-render on store or messages
|
|
376
|
+
| Topic | Notes |
|
|
377
|
+
| ---------------- | --------------------------------------------------------------------------------------------------------- |
|
|
378
|
+
| Immutable config | `url`, `reconnectMs`, etc. are fixed at create time; to use a different URL, call `createWsContext` again |
|
|
379
|
+
| Reconnect | Fixed interval only; no exponential backoff; optional cap via `reconnectMax` |
|
|
380
|
+
| SSR | No `WebSocket` on the server; `connect()` is a no-op without `window` |
|
|
381
|
+
| Error status | No `"error"` in `WsStatus`; use `useWsEvents("error")` |
|
|
382
|
+
| `WsState` scope | Health / queue / reconnect only — not messages or app data |
|
|
383
|
+
| Rendering | Components that only call `useWsActions` do not re-render on store or messages |
|
|
384
|
+
| Store updates | Repeated writes of the same field values do not notify; prefer selectors |
|
|
362
385
|
|
|
363
386
|
---
|
|
364
387
|
|
|
@@ -377,13 +400,13 @@ This package does **not** list zustand or nanoevents as npm dependencies. It inl
|
|
|
377
400
|
- **Maintainer:** [pmndrs](https://github.com/pmndrs) (Poimandres)
|
|
378
401
|
- **License:** [MIT](https://github.com/pmndrs/zustand/blob/main/LICENSE)
|
|
379
402
|
- **Adapted from:**
|
|
380
|
-
- External store API — aligned with [`vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts) (subset only)
|
|
381
|
-
- React subscription — inspired by [`react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts) `useStore`
|
|
403
|
+
- External store API — aligned with [`vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts) (subset only; no middleware, replace, or initializer factory); `setState` adds partial shallow dedup (unchanged values skip notification)
|
|
404
|
+
- React subscription — inspired by [`react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts) `useStore` (no `useDebugValue`); optional selector overload lives in `createUseWsStore`
|
|
382
405
|
- **Files:** `src/ws-context/store.ts`, `src/ws-context/use-store.ts`
|
|
383
406
|
|
|
384
407
|
### [nanoevents](https://github.com/ai/nanoevents)
|
|
385
408
|
|
|
386
409
|
- **Author:** [Andrey Sitnik](https://github.com/ai) (`ai`)
|
|
387
410
|
- **License:** [MIT](https://github.com/ai/nanoevents/blob/main/LICENSE)
|
|
388
|
-
- **Adapted from:** [`createNanoEvents`](https://github.com/ai/nanoevents/blob/main/index.js);
|
|
389
|
-
- **
|
|
411
|
+
- **Adapted from:** [`createNanoEvents`](https://github.com/ai/nanoevents/blob/main/index.js); React subscription wrapper added in `ws-events.ts`
|
|
412
|
+
- **Files:** `src/ws-context/emitter.ts`, `src/ws-context/ws-events.ts`
|