chem-rx 0.0.18 → 0.0.19
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/README.md +54 -2
- package/dist/Signal.d.ts +6 -6
- package/dist/Signal.d.ts.map +1 -1
- package/dist/index.cjs.js +37 -1
- package/dist/index.iife.js +37 -1
- package/dist/index.js +33 -2
- package/package.json +1 -1
- package/src/Signal.ts +29 -11
- package/tests/atom.test.ts +78 -0
package/README.md
CHANGED
|
@@ -245,6 +245,15 @@ signal$.ping("hello")
|
|
|
245
245
|
subscription.unsubscribe();
|
|
246
246
|
```
|
|
247
247
|
|
|
248
|
+
You can selectively subscribe to Signals, and selectively ping subscribers by ID.
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
const signal = new Signal<string>();
|
|
252
|
+
signal.subscribe(mockCallbackId123, "123");
|
|
253
|
+
signal.subscribe(mockCallbackId456, "456");
|
|
254
|
+
signal.ping("Message for 123", "123");
|
|
255
|
+
```
|
|
256
|
+
|
|
248
257
|
## Use with React
|
|
249
258
|
|
|
250
259
|
### useAtom
|
|
@@ -287,23 +296,66 @@ function Counter() {
|
|
|
287
296
|
<button onClick={() => count$.set('inner', count + 2)}>one up</button> ...
|
|
288
297
|
```
|
|
289
298
|
|
|
290
|
-
###
|
|
299
|
+
### hydrateAtoms
|
|
291
300
|
|
|
292
301
|
With SSR, your atoms will likely need to be properly hydrated to prevent
|
|
293
302
|
server/client mismatches. You can use `useHydrateAtoms` as a simple solution for
|
|
294
303
|
seeding your client-side Atoms with the correct data.
|
|
295
304
|
|
|
305
|
+
NOTE: **`hydrateAtoms` caches values and only runs on the initial load by default**, to prevent re-hydration when client-side only changes are made to the component.
|
|
306
|
+
|
|
296
307
|
```
|
|
297
308
|
import { Atom, useAtom, useHydrateAtoms } from 'chem-rx'
|
|
298
309
|
|
|
299
310
|
const count$ = Atom(0)
|
|
300
311
|
const CounterPage = ({ countFromServer }) => {
|
|
301
|
-
|
|
312
|
+
hydrateAtoms([[count$, countFromServer]])
|
|
302
313
|
const count = useAtom(count$)
|
|
303
314
|
// count would be the value of `countFromServer`, not 0.
|
|
315
|
+
|
|
316
|
+
/*
|
|
317
|
+
* ... other code
|
|
318
|
+
*/
|
|
319
|
+
|
|
320
|
+
useEffect(() => {
|
|
321
|
+
// This is safe, because hydrateAtoms runs once by default
|
|
322
|
+
count$.next(10)
|
|
323
|
+
}, [otherDeps])
|
|
324
|
+
|
|
304
325
|
}
|
|
305
326
|
```
|
|
306
327
|
|
|
328
|
+
#### `force` hydrateAtoms
|
|
329
|
+
|
|
330
|
+
If you want to force re-hydration, you can optionally pass in a `{force: true}` (for example - you have a top level page wrapper that receives server data )
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
export default function CasesPage({ cases }: Props) {
|
|
334
|
+
// force it to rehydrate with newest value every time this client page is loaded
|
|
335
|
+
hydrateAtoms([[cases$, cases]], { force: true });
|
|
336
|
+
const router = useRouter();
|
|
337
|
+
return (
|
|
338
|
+
<>
|
|
339
|
+
<div className="flex justify-between w-full mb-4 px-6 pt-8">
|
|
340
|
+
<h1 className="text-2xl">Cases</h1>
|
|
341
|
+
<Button
|
|
342
|
+
onClick={() => {
|
|
343
|
+
router.push("/cases/new");
|
|
344
|
+
}}
|
|
345
|
+
>
|
|
346
|
+
<PlusCircle className="w-4 h-4 mr-2" /> Add Cases
|
|
347
|
+
</Button>
|
|
348
|
+
</div>
|
|
349
|
+
<CasesTable />
|
|
350
|
+
</>
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
In this example, `CasePage` is a top level client component rendering the home page from a SPA, which gets redirected to when coming from another page. We want it to render with the latest server-rendered data.
|
|
356
|
+
|
|
357
|
+
NOTE: `force` should only be used when the component is expected to only re-render when new data comes from the server - **and never anytime else**.
|
|
358
|
+
|
|
307
359
|
## Suggested Usage
|
|
308
360
|
|
|
309
361
|
There are several suggested "patterns" when using Atoms:
|
package/dist/Signal.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare class
|
|
3
|
-
|
|
1
|
+
import { Subscription } from "rxjs";
|
|
2
|
+
export declare class Signal<T = any> {
|
|
3
|
+
private _subjects;
|
|
4
|
+
private _defaultSubject;
|
|
4
5
|
constructor();
|
|
5
|
-
ping(value: T): void;
|
|
6
|
-
subscribe(
|
|
6
|
+
ping(value: T, id?: string): void;
|
|
7
|
+
subscribe(callback: (value: T) => void, id?: string): Subscription;
|
|
7
8
|
}
|
|
8
|
-
export declare function Signal<T>(): void;
|
|
9
9
|
//# sourceMappingURL=Signal.d.ts.map
|
package/dist/Signal.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Signal.d.ts","sourceRoot":"","sources":["../src/Signal.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"Signal.d.ts","sourceRoot":"","sources":["../src/Signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,YAAY,EAAE,MAAM,MAAM,CAAC;AAE7C,qBAAa,MAAM,CAAC,CAAC,GAAG,GAAG;IACzB,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,eAAe,CAAa;;IAOpC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM;IAY1B,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,YAAY;CAcnE"}
|
package/dist/index.cjs.js
CHANGED
|
@@ -335,7 +335,43 @@ function hydrateAtoms(values, options) {
|
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
function
|
|
338
|
+
var Signal = /*#__PURE__*/function () {
|
|
339
|
+
function Signal() {
|
|
340
|
+
this._subjects = new Map();
|
|
341
|
+
this._defaultSubject = new rxjs.Subject();
|
|
342
|
+
}
|
|
343
|
+
var _proto = Signal.prototype;
|
|
344
|
+
_proto.ping = function ping(value, id) {
|
|
345
|
+
if (id && this._subjects.has(id)) {
|
|
346
|
+
var _this$_subjects$get;
|
|
347
|
+
// If an ID is provided and exists, notify only that subscription.
|
|
348
|
+
(_this$_subjects$get = this._subjects.get(id)) == null ? void 0 : _this$_subjects$get.next(value);
|
|
349
|
+
} else {
|
|
350
|
+
// No ID provided, notify all default subscribers.
|
|
351
|
+
this._defaultSubject.next(value);
|
|
352
|
+
// Additionally, notify all subscriptions as a broadcast.
|
|
353
|
+
this._subjects.forEach(function (subject) {
|
|
354
|
+
return subject.next(value);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
_proto.subscribe = function subscribe(callback, id) {
|
|
359
|
+
if (id) {
|
|
360
|
+
// If an ID is provided, subscribe to the specific ID.
|
|
361
|
+
if (!this._subjects.has(id)) {
|
|
362
|
+
this._subjects.set(id, new rxjs.Subject());
|
|
363
|
+
}
|
|
364
|
+
return this._subjects.get(id).subscribe(callback);
|
|
365
|
+
} else {
|
|
366
|
+
// No ID provided, subscribe to the default subject.
|
|
367
|
+
return this._defaultSubject.subscribe(callback);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Optionally, implement unsubscribe logic to manage subscriptions.
|
|
372
|
+
;
|
|
373
|
+
return Signal;
|
|
374
|
+
}();
|
|
339
375
|
|
|
340
376
|
exports.Atom = Atom;
|
|
341
377
|
exports.Signal = Signal;
|
package/dist/index.iife.js
CHANGED
|
@@ -333,7 +333,43 @@ var chemicalRx = (function (exports, rxjs, react) {
|
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
-
function
|
|
336
|
+
var Signal = /*#__PURE__*/function () {
|
|
337
|
+
function Signal() {
|
|
338
|
+
this._subjects = new Map();
|
|
339
|
+
this._defaultSubject = new rxjs.Subject();
|
|
340
|
+
}
|
|
341
|
+
var _proto = Signal.prototype;
|
|
342
|
+
_proto.ping = function ping(value, id) {
|
|
343
|
+
if (id && this._subjects.has(id)) {
|
|
344
|
+
var _this$_subjects$get;
|
|
345
|
+
// If an ID is provided and exists, notify only that subscription.
|
|
346
|
+
(_this$_subjects$get = this._subjects.get(id)) == null ? void 0 : _this$_subjects$get.next(value);
|
|
347
|
+
} else {
|
|
348
|
+
// No ID provided, notify all default subscribers.
|
|
349
|
+
this._defaultSubject.next(value);
|
|
350
|
+
// Additionally, notify all subscriptions as a broadcast.
|
|
351
|
+
this._subjects.forEach(function (subject) {
|
|
352
|
+
return subject.next(value);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
_proto.subscribe = function subscribe(callback, id) {
|
|
357
|
+
if (id) {
|
|
358
|
+
// If an ID is provided, subscribe to the specific ID.
|
|
359
|
+
if (!this._subjects.has(id)) {
|
|
360
|
+
this._subjects.set(id, new rxjs.Subject());
|
|
361
|
+
}
|
|
362
|
+
return this._subjects.get(id).subscribe(callback);
|
|
363
|
+
} else {
|
|
364
|
+
// No ID provided, subscribe to the default subject.
|
|
365
|
+
return this._defaultSubject.subscribe(callback);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Optionally, implement unsubscribe logic to manage subscriptions.
|
|
370
|
+
;
|
|
371
|
+
return Signal;
|
|
372
|
+
}();
|
|
337
373
|
|
|
338
374
|
exports.Atom = Atom;
|
|
339
375
|
exports.Signal = Signal;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { combineLatest, isObservable, BehaviorSubject, map, distinctUntilChanged } from 'rxjs';
|
|
1
|
+
import { combineLatest, isObservable, BehaviorSubject, map, distinctUntilChanged, Subject } from 'rxjs';
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
3
|
|
|
4
4
|
function _extends() {
|
|
@@ -248,6 +248,37 @@ function hydrateAtoms(values, options) {
|
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
|
|
251
|
+
class Signal {
|
|
252
|
+
constructor() {
|
|
253
|
+
this._subjects = new Map();
|
|
254
|
+
this._defaultSubject = new Subject();
|
|
255
|
+
}
|
|
256
|
+
ping(value, id) {
|
|
257
|
+
if (id && this._subjects.has(id)) {
|
|
258
|
+
var _this$_subjects$get;
|
|
259
|
+
// If an ID is provided and exists, notify only that subscription.
|
|
260
|
+
(_this$_subjects$get = this._subjects.get(id)) == null ? void 0 : _this$_subjects$get.next(value);
|
|
261
|
+
} else {
|
|
262
|
+
// No ID provided, notify all default subscribers.
|
|
263
|
+
this._defaultSubject.next(value);
|
|
264
|
+
// Additionally, notify all subscriptions as a broadcast.
|
|
265
|
+
this._subjects.forEach(subject => subject.next(value));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
subscribe(callback, id) {
|
|
269
|
+
if (id) {
|
|
270
|
+
// If an ID is provided, subscribe to the specific ID.
|
|
271
|
+
if (!this._subjects.has(id)) {
|
|
272
|
+
this._subjects.set(id, new Subject());
|
|
273
|
+
}
|
|
274
|
+
return this._subjects.get(id).subscribe(callback);
|
|
275
|
+
} else {
|
|
276
|
+
// No ID provided, subscribe to the default subject.
|
|
277
|
+
return this._defaultSubject.subscribe(callback);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Optionally, implement unsubscribe logic to manage subscriptions.
|
|
282
|
+
}
|
|
252
283
|
|
|
253
284
|
export { Atom, Signal, hydrateAtoms, useAtom, useSelectAtom };
|
package/package.json
CHANGED
package/src/Signal.ts
CHANGED
|
@@ -1,20 +1,38 @@
|
|
|
1
|
-
import { Subject } from "rxjs";
|
|
1
|
+
import { Subject, Subscription } from "rxjs";
|
|
2
2
|
|
|
3
|
-
export class
|
|
4
|
-
|
|
3
|
+
export class Signal<T = any> {
|
|
4
|
+
private _subjects: Map<string, Subject<T>>;
|
|
5
|
+
private _defaultSubject: Subject<T>;
|
|
5
6
|
|
|
6
7
|
constructor() {
|
|
7
|
-
|
|
8
|
-
this.
|
|
8
|
+
this._subjects = new Map();
|
|
9
|
+
this._defaultSubject = new Subject<T>();
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
ping(value: T) {
|
|
12
|
-
this.
|
|
12
|
+
ping(value: T, id?: string) {
|
|
13
|
+
if (id && this._subjects.has(id)) {
|
|
14
|
+
// If an ID is provided and exists, notify only that subscription.
|
|
15
|
+
this._subjects.get(id)?.next(value);
|
|
16
|
+
} else {
|
|
17
|
+
// No ID provided, notify all default subscribers.
|
|
18
|
+
this._defaultSubject.next(value);
|
|
19
|
+
// Additionally, notify all subscriptions as a broadcast.
|
|
20
|
+
this._subjects.forEach((subject) => subject.next(value));
|
|
21
|
+
}
|
|
13
22
|
}
|
|
14
23
|
|
|
15
|
-
subscribe(
|
|
16
|
-
|
|
24
|
+
subscribe(callback: (value: T) => void, id?: string): Subscription {
|
|
25
|
+
if (id) {
|
|
26
|
+
// If an ID is provided, subscribe to the specific ID.
|
|
27
|
+
if (!this._subjects.has(id)) {
|
|
28
|
+
this._subjects.set(id, new Subject<T>());
|
|
29
|
+
}
|
|
30
|
+
return this._subjects.get(id)!.subscribe(callback);
|
|
31
|
+
} else {
|
|
32
|
+
// No ID provided, subscribe to the default subject.
|
|
33
|
+
return this._defaultSubject.subscribe(callback);
|
|
34
|
+
}
|
|
17
35
|
}
|
|
18
|
-
}
|
|
19
36
|
|
|
20
|
-
|
|
37
|
+
// Optionally, implement unsubscribe logic to manage subscriptions.
|
|
38
|
+
}
|
package/tests/atom.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ArrayAtom, Atom, BaseAtom, ReadOnlyAtom } from "../src/Atom";
|
|
2
|
+
import { Signal } from "../src/Signal";
|
|
2
3
|
import { BehaviorSubject, map } from "rxjs";
|
|
3
4
|
|
|
4
5
|
test("Base Atom values test", () => {
|
|
@@ -540,6 +541,83 @@ test("Test select nullable object", () => {
|
|
|
540
541
|
expect(stacySchool.get("graduation")).toBe(2014);
|
|
541
542
|
});
|
|
542
543
|
|
|
544
|
+
describe("SignalWithId", () => {
|
|
545
|
+
it("should broadcast messages to all subscribers when no ID is provided", (done) => {
|
|
546
|
+
const signal = new Signal<string>();
|
|
547
|
+
const mockCallback = jest.fn();
|
|
548
|
+
|
|
549
|
+
signal.subscribe(mockCallback);
|
|
550
|
+
signal.subscribe(mockCallback);
|
|
551
|
+
|
|
552
|
+
signal.ping("test message");
|
|
553
|
+
|
|
554
|
+
setImmediate(() => {
|
|
555
|
+
expect(mockCallback).toHaveBeenCalledTimes(2);
|
|
556
|
+
expect(mockCallback).toHaveBeenCalledWith("test message");
|
|
557
|
+
done();
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it("should send messages only to subscribers with the specific ID", (done) => {
|
|
562
|
+
const signal = new Signal<string>();
|
|
563
|
+
const mockCallbackWithId = jest.fn();
|
|
564
|
+
const mockCallbackWithoutId = jest.fn();
|
|
565
|
+
|
|
566
|
+
signal.subscribe(mockCallbackWithId, "123");
|
|
567
|
+
signal.subscribe(mockCallbackWithoutId);
|
|
568
|
+
|
|
569
|
+
signal.ping("ID specific message", "123");
|
|
570
|
+
|
|
571
|
+
setImmediate(() => {
|
|
572
|
+
expect(mockCallbackWithId).toHaveBeenCalledTimes(1);
|
|
573
|
+
expect(mockCallbackWithId).toHaveBeenCalledWith("ID specific message");
|
|
574
|
+
expect(mockCallbackWithoutId).toHaveBeenCalledTimes(0);
|
|
575
|
+
done();
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it("should not notify subscribers with different IDs", (done) => {
|
|
580
|
+
const signal = new Signal<string>();
|
|
581
|
+
const mockCallbackId123 = jest.fn();
|
|
582
|
+
const mockCallbackId456 = jest.fn();
|
|
583
|
+
|
|
584
|
+
signal.subscribe(mockCallbackId123, "123");
|
|
585
|
+
signal.subscribe(mockCallbackId456, "456");
|
|
586
|
+
|
|
587
|
+
signal.ping("Message for 123", "123");
|
|
588
|
+
|
|
589
|
+
setImmediate(() => {
|
|
590
|
+
expect(mockCallbackId123).toHaveBeenCalledTimes(1);
|
|
591
|
+
expect(mockCallbackId123).toHaveBeenCalledWith("Message for 123");
|
|
592
|
+
expect(mockCallbackId456).toHaveBeenCalledTimes(0);
|
|
593
|
+
done();
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it("should notify all subscribers, including those with specific IDs, when pinging without an ID", (done) => {
|
|
598
|
+
const signal = new Signal<string>();
|
|
599
|
+
const mockCallback = jest.fn();
|
|
600
|
+
const mockCallbackWithId = jest.fn();
|
|
601
|
+
|
|
602
|
+
// Subscribe one callback without ID (for general broadcast)
|
|
603
|
+
signal.subscribe(mockCallback);
|
|
604
|
+
// Subscribe another callback with a specific ID
|
|
605
|
+
signal.subscribe(mockCallbackWithId, "123");
|
|
606
|
+
|
|
607
|
+
// Ping without specifying an ID should notify both subscribers
|
|
608
|
+
signal.ping("broadcast message");
|
|
609
|
+
|
|
610
|
+
setImmediate(() => {
|
|
611
|
+
// Both callbacks should be called once
|
|
612
|
+
expect(mockCallback).toHaveBeenCalledTimes(1);
|
|
613
|
+
expect(mockCallback).toHaveBeenCalledWith("broadcast message");
|
|
614
|
+
expect(mockCallbackWithId).toHaveBeenCalledTimes(1);
|
|
615
|
+
expect(mockCallbackWithId).toHaveBeenCalledWith("broadcast message");
|
|
616
|
+
done();
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
|
|
543
621
|
/*
|
|
544
622
|
* TODO:
|
|
545
623
|
* - test react hooks
|