Skip to content

Commit 934b1f4

Browse files
committed
[IMPL] useBroadcastChannel hook
1 parent a469fe3 commit 934b1f4

8 files changed

Lines changed: 163 additions & 25 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { FormEvent } from "react";
2+
import { useBroadcastChannel } from "../../../../../../packages/react-tools/src"
3+
4+
/**
5+
The component uses _useBroadcastChannel_ hook to send a text in a broadcast channel
6+
*/
7+
export const UseBroadcastChannel = () => {
8+
const [state, setState] = useBroadcastChannel<string>("react-tools");
9+
10+
return <div>
11+
<h2>Open page on multiple tab to see how hook work</h2>
12+
<p>State: {state}</p>
13+
<form
14+
onSubmit={(e: FormEvent<HTMLFormElement>) => {
15+
e.preventDefault();
16+
setState(((e.target as HTMLFormElement).elements.namedItem("text") as HTMLInputElement).value)
17+
}}
18+
>
19+
<input name="text" type="text" />
20+
<button type="submit">SEND</button>
21+
</form>
22+
</div>
23+
}

apps/react-tools-demo/src/constants/components.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ export const COMPONENTS = [
120120
"useWebWorkerFn",
121121
"usePromiseSuspensible",
122122
"useFetch",
123-
"useLock"
123+
"useLock",
124+
"useBroadcastChannel"
124125
]
125126
],
126127
//UTILS
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# useBroadcastChannel
2+
Hook to use [Broadcast Channel API](https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API).
3+
4+
## Usage
5+
6+
```tsx
7+
export const UseBroadcastChannel = () => {
8+
const [state, setState] = useBroadcastChannel<string>("react-tools");
9+
10+
return <div>
11+
<h2>Open page on multiple tab to see how hook work</h2>
12+
<p>State: {state}</p>
13+
<form onSubmit={(e: FormEvent<HTMLFormElement>) => { debugger }}>
14+
<input name="text" type="text" />
15+
<button type="submit">SEND</button>
16+
</form>
17+
</div>
18+
}
19+
```
20+
21+
> The component uses _useBroadcastChannel_ hook to send a text in a broadcast channel
22+
23+
24+
## API
25+
26+
```tsx
27+
useBroadcastChannel<T>(name: string, onMessage?: (evt:MessageEvent<T>)=>void, onError?: (evt: MessageEvent)=>void):[T|undefined, (data:T)=>void]
28+
```
29+
30+
> ### Params
31+
>
32+
> - __name__: _string_
33+
broadcast channel name.
34+
> - __onMessage?__: _(evt:MessageEvent)=>void_
35+
function that will be execute when a message occurred.
36+
> - __onError?__: _(evt:MessageEvent)=>void_
37+
function that will be execute when a error message occurred.
38+
>
39+
40+
> ### Returns
41+
>
42+
> __result__: __Array__:
43+
- _T|undefined_
44+
- _(data:T)=>void_
45+
> Array of:
46+
> - first element: __data__ received in broadcast channel.
47+
> - second element: __send__ function to send data on broadcast channel.
48+
>

apps/react-tools-demo/src/markdown/useLock.md

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,37 @@ Hook to use [Web Locks API](https://developer.mozilla.org/en-US/docs/Web/API/Web
77
export const UseLock = () => {
88
const [buffer, setBuffer] = useState<number[]>([]);
99
const [lock, setLock] = useState<{ held: string[], pending: string[] }>({ held: [], pending: [] });
10-
const [messages, setMessages] = useState<{ consumer: string[], producer: string[] }>({
10+
const [messages, setMessages] = useState<{ consumer: string[], buffer: number[][], producer: string[] }>({
1111
consumer: [],
12+
buffer: [],
1213
producer: []
1314
});
1415
const do_something = useCallback((mode: "read" | "write") => {
1516
return new Promise<void>((res) => {
1617
setTimeout(() => {
1718
if (mode === "read") {
1819
let el: number | undefined;
19-
setBuffer(b => b.filter((_, index, arr) => {
20-
if (index !== arr.length - 1) {
21-
return true;
22-
} else {
23-
el = _;
24-
return false;
25-
}
26-
}))
27-
setMessages(m => ({...m, consumer: [...m.consumer, el !== undefined ? "Consumer has read " + el : "Consumer has read nothing"]}))
20+
let buffer: number[];
21+
setBuffer(b => {
22+
buffer = b.filter((_, index, arr) => {
23+
if (index !== arr.length - 1) {
24+
return true;
25+
} else {
26+
el = _;
27+
return false;
28+
}
29+
});
30+
return buffer;
31+
})
32+
setMessages(m => ({ producer: [...m.producer, "-"].filter((_, index, arr) => arr.length - index <= 5), buffer: [...m.buffer, buffer].filter((_,index,arr)=>arr.length-index<=5), consumer: [...m.consumer, el !== undefined ? "Consumer has read " + el : "Consumer has read nothing"].filter((_,index, arr) => arr.length-index<=5)}))
2833
} else {
2934
const n = Math.floor(Math.random() * 11);
30-
setBuffer(b => [n, ...b]);
31-
setMessages(m => ({ ...m, producer: [...m.producer, "Producer has written " + n] }));
35+
let buffer: number[];
36+
setBuffer(b => {
37+
buffer = [n, ...b];
38+
return buffer;
39+
});
40+
setMessages(m => ({ consumer: [...m.consumer, "-"].filter((_, index, arr) => arr.length - index <= 5), buffer: [...m.buffer, buffer].filter((_, index, arr) => arr.length - index <= 5), producer: [...m.producer, "Producer has written " + n].filter((_, index, arr) => arr.length - index <= 5) }));
3241
}
3342
res();
3443
}, 1600);
@@ -46,16 +55,16 @@ export const UseLock = () => {
4655
useEffect(() => {
4756
const interval = setInterval(async () => {
4857
const n = Math.floor(Math.random() * 11);
49-
n > 6 ? createExclusiveLock() : createSharedLock();
58+
n <= 6 ? createExclusiveLock() : createSharedLock();
5059
}, 700);
5160
return () => clearInterval(interval);
5261
}, [createExclusiveLock, createSharedLock]);
5362

5463
useEffect(() => {
5564
const interval = setInterval(async () => {
5665
const result = await query();
57-
const held = (result.held || []).map(el => el.name + " - " + el.mode);
58-
const pending = (result.pending || []).map(el => el.name + " - " + el.mode);
66+
const held = (result.held || []).map(el => `${el.mode === "exclusive" ? "Reader" : "Writer"} require ${el.mode} lock`);
67+
const pending = (result.pending || []).map(el => `${el.mode === "exclusive" ? "Reader" : "Writer"} require ${el.mode} lock`);
5968
setLock({ held, pending });
6069
}, 1000)
6170
return () => {
@@ -73,7 +82,9 @@ export const UseLock = () => {
7382
</div>
7483
<div>
7584
<h3>Buffer</h3>
76-
<p>{JSON.stringify(buffer, null, 6)}</p>
85+
{
86+
messages.buffer.map((m, index) => <p key={index}>{JSON.stringify(m)}</p>)
87+
}
7788
</div>
7889
<div style={{ display: "grid", gridTemplateColumns: "auto", justifyContent: "center", gap: 50, overflow: 'auto', maxHeight: 400 }}>
7990
<div>

packages/react-tools/README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
- [x] useSyncExternalStore
1717
- [x] useDerivedState
1818
- [ ] useStateValidator (???)
19+
- [ ] useObservable — (https://netbasal.com/javascript-observables-under-the-hood-2423f760584)
1920
- [ ] useSignal (https://medium.com/@personal.david.kohen/the-quest-for-signals-in-react-usestate-on-steroids-71eb9fc87c14)
20-
- [ ] useObservable — tracks latest value of an Observable
21-
- [ ] useStore
22-
- [ ] createStore (example: https://github.com/streamich/react-use/blob/master/src/factory/createGlobalState.ts)
23-
- [ ] usePubSubStore (with pusSub model)
24-
- [ ] useBroadcast (refer to https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API - https://vueuse.org/core/useBroadcastChannel/)
21+
- [ ] createSignal (https://medium.com/@personal.david.kohen/the-quest-for-signals-in-react-usestate-on-steroids-71eb9fc87c14)
22+
- [ ] usePubSubStore (with pusSub model)
23+
- [ ] createPubSubStore (with pusSub model)
24+
- [ ] useProxyStore (TODO: https://github.com/streamich/react-use/blob/master/src/factory/createGlobalState.ts)
25+
- [ ] createProxyStore (TODO: https://github.com/streamich/react-use/blob/master/src/factory/createGlobalState.ts)
2526

2627
- __LIFECYCLE__
2728
- [x] useEffectCompare
@@ -123,7 +124,8 @@
123124
- [x] usePromiseSuspensible
124125
- [x] useFetch
125126
- [x] useLock
126-
- [ ] useIndexedDB (TODO)
127+
- [x] useBroadcastChannel
128+
- [ ] useIndexedDB (TODO: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API?retiredLocale=it)
127129
- [ ] useIdleDetection (not work yet. https://developer.mozilla.org/en-US/docs/Web/API/Idle_Detection_API)
128130

129131
- __UTILS__

packages/react-tools/src/hooks/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,5 @@ export { useWebWorker } from './useWebWorker';
101101
export { useWebWorkerFn } from './useWebWorkerFn';
102102
export { usePromiseSuspensible } from './usePromiseSuspensible';
103103
export { useFetch } from './useFetch';
104-
export { useLock } from './useLock';
104+
export { useLock } from './useLock';
105+
export { useBroadcastChannel } from './useBroadcastChannel';
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useCallback, useLayoutEffect, useMemo, useRef } from "react"
2+
import { useSyncExternalStore } from ".";
3+
4+
/**
5+
* **`useBroadcastChannel`**: Hook to use [Broadcast Channel API](https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API).
6+
* @param {string} name - broadcast channel name.
7+
* @param {(evt:MessageEvent)=>void} [onMessage] - function that will be execute when a message occurred.
8+
* @param {(evt:MessageEvent)=>void} [onError] - function that will be execute when a error message occurred.
9+
* @returns {[T|undefined, (data:T)=>void]} result
10+
* Array of:
11+
* - first element: __data__ received in broadcast channel.
12+
* - second element: __send__ function to send data on broadcast channel.
13+
*/
14+
export const useBroadcastChannel = <T>(name: string, onMessage?: (evt:MessageEvent<T>)=>void, onError?: (evt: MessageEvent)=>void):[T|undefined, (data:T)=>void] => {
15+
const notifyRef = useRef<() => void>();
16+
const dataRef = useRef<T>();
17+
const bcRef = useRef<BroadcastChannel>();
18+
const onMessageCb = useCallback((evt: MessageEvent<T>) => {
19+
dataRef.current = evt.data;
20+
!!notifyRef.current && notifyRef.current();
21+
!!onMessage && onMessage(evt);
22+
}, [onMessage]);
23+
24+
const send = useCallback((data: T) => {
25+
!!bcRef.current && bcRef.current.postMessage(data);
26+
}, []);
27+
28+
const data = useSyncExternalStore(
29+
useCallback(notif => {
30+
notifyRef.current = notif;
31+
return () => notifyRef.current = undefined;
32+
}, []),
33+
useMemo(() => {
34+
let cachedData = dataRef.current;
35+
return () => {
36+
if (cachedData !== dataRef.current) {
37+
cachedData = dataRef.current;
38+
}
39+
return cachedData;
40+
}
41+
}, [])
42+
);
43+
44+
useLayoutEffect(() => {
45+
bcRef.current = new BroadcastChannel(name);
46+
!!onError && (bcRef.current.onmessageerror = onError);
47+
bcRef.current.onmessage = onMessageCb;
48+
}, [name, onMessageCb, onError]);
49+
50+
return [data, send];
51+
}

packages/react-tools/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ export {
198198
useWebWorkerFn,
199199
usePromiseSuspensible,
200200
useFetch,
201-
useLock
201+
useLock,
202+
useBroadcastChannel
202203
} from './hooks'
203204

204205
export {

0 commit comments

Comments
 (0)