1 |
|
2 | import { useEffect, useMemo, useState } from "preact/hooks";
|
3 | const LISTENERS = Symbol();
|
4 | const SNAPSHOT = Symbol();
|
5 | const isObject = (x) => typeof x === "object" && x !== null;
|
6 | const createProxy = (initialObject = {}) => {
|
7 | let version = 0;
|
8 | const listeners = new Set();
|
9 | const incrementVersion = () => {
|
10 | version = version + 1;
|
11 | listeners.forEach((listener) => listener());
|
12 | };
|
13 | const proxy = new Proxy(Object.create(initialObject.constructor.prototype), {
|
14 | get(target, prop) {
|
15 | if (prop === LISTENERS) {
|
16 | return listeners;
|
17 | }
|
18 | if (prop === SNAPSHOT) {
|
19 | const snapshot = Object.create(target.constructor.prototype);
|
20 | Reflect.ownKeys(target).forEach((key) => {
|
21 | const value = target[key];
|
22 | if (isObject(value)) {
|
23 | snapshot[key] = value[SNAPSHOT];
|
24 | }
|
25 | else {
|
26 | snapshot[key] = value;
|
27 | }
|
28 | });
|
29 | return snapshot;
|
30 | }
|
31 | return target[prop];
|
32 | },
|
33 | deleteProperty(target, prop) {
|
34 | const value = target[prop];
|
35 | if (isObject(value)) {
|
36 | value[LISTENERS].delete(incrementVersion);
|
37 | }
|
38 | delete target[prop];
|
39 | incrementVersion();
|
40 | return true;
|
41 | },
|
42 | set(target, prop, value) {
|
43 | if (isObject(value)) {
|
44 | if (value[LISTENERS]) {
|
45 | target[prop] = value;
|
46 | }
|
47 | else {
|
48 | target[prop] = createProxy(value);
|
49 | }
|
50 | target[prop][LISTENERS].add(incrementVersion);
|
51 | }
|
52 | else {
|
53 | target[prop] = value;
|
54 | }
|
55 | incrementVersion();
|
56 | return true;
|
57 | },
|
58 | });
|
59 | Reflect.ownKeys(initialObject).forEach((key) => {
|
60 | proxy[key] = initialObject[key];
|
61 | });
|
62 | return proxy;
|
63 | };
|
64 | const subscribe = (proxy, callback) => {
|
65 | proxy[LISTENERS].add(callback);
|
66 | return () => {
|
67 | proxy[LISTENERS].delete(callback);
|
68 | };
|
69 | };
|
70 | const getSnapshot = (proxy) => proxy[SNAPSHOT];
|
71 | export const createGlobalState = createProxy;
|
72 | export const useGlobalState = (source = {}) => {
|
73 | const subscription = useMemo(() => ({
|
74 | getCurrentValue: () => getSnapshot(source),
|
75 | subscribe: (callback) => subscribe(source, callback),
|
76 | }), [source]);
|
77 | const [state, setState] = useState(() => ({
|
78 | value: subscription.getCurrentValue(),
|
79 | }));
|
80 | let valueToReturn = state.value;
|
81 | useEffect(() => {
|
82 | let didUnsubscribe = false;
|
83 | const checkForUpdates = () => {
|
84 | if (didUnsubscribe)
|
85 | return;
|
86 | const value = subscription.getCurrentValue();
|
87 | setState((prevState) => {
|
88 | if (prevState.value === value) {
|
89 | return prevState;
|
90 | }
|
91 | return { value };
|
92 | });
|
93 | };
|
94 | let unsubscribe = subscription.subscribe(checkForUpdates);
|
95 | checkForUpdates();
|
96 | return () => {
|
97 | didUnsubscribe = true;
|
98 | unsubscribe();
|
99 | };
|
100 | }, [subscription]);
|
101 | return valueToReturn;
|
102 | };
|