Skip to content

Commit 6a530e1

Browse files
committed
[FIX] createPubSubStore hook
1 parent c82a921 commit 6a530e1

3 files changed

Lines changed: 51 additions & 24 deletions

File tree

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ boolean that indicates if the store value will be persisted on the local Storage
8686
- __usePubSubStore__ : _<C>(subscribe?: (store: T) => C)=>[T|C, (store: T|C|((currStore: T) => T)|((currStore: C) => C)) => void, () => T]_
8787
> An object with three _immutable_ functions:
8888
> - __getStore__: function that returns the store object.
89-
> - __updateStore__: function that updates the store: it receives a callback with an only parameter, the store, and return void. Inside this function it can possible to modify to any store properties: changes will be notified to every user of the store.
90-
> - __usePubSubStore__: It's the hook to be used inside components to access the store. It receives an optional callback _subscribe_ to specify to which part of store you want access and to be notified of its changes. If no callback is gived, it returns the whole store. It returns an array of three elements:
91-
> - _first element_: the __state__. Represents the slice of store indicated by _subscribe_ callback or the whole store.
92-
> - _second element_: the __updateState__. An _immutable_ function that represents the function to update the state. Depending on whether the hook was invoked by passing the _subscribe_ callback or not, it receives a new value for the slice or the entire store or a callback with only one parameter, the current slice or store, which returns a new slice or value of the shop.
93-
> - _third element_: the __getState__. An _immutable_ function that returns the current state returned by hook that can be a slice or the whole store, depending on the present of the _subscribe_ callback.
89+
> - __updateStore__: function that updates the store: it receives a callback with an only parameter, the store, and return void. Inside this function it can be possible to modify any store properties: changes will be published to every subscriber.
90+
> - __usePubSubStore__: It's the hook to be used inside components to access the store. It receives an optional callback _subscribe_ to specify to which part of store you want to subscribe.If callback missed, the whole store will be subscribed. It returns an array of three elements:
91+
> - _first element_: the __state__. It represents what has been subscribed.
92+
> - _second element_: the __updateState__. An _immutable_ function that represents the function to update the state. It can be executed given it a new version of the subscribed value or with a callback that receives the subscribed value and returns a new version of it.
93+
> - _third element_: the __getState__. An _immutable_ function that returns the current subscribed value.
9494
>

packages/react-tools/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
- [x] useDerivedState
1818
- [x] useStateValidator
1919
- [x] createPubSubStore
20-
- [ ] useObservable — (https://netbasal.com/javascript-observables-under-the-hood-2423f760584)
2120
- [ ] useSignal (https://medium.com/@personal.david.kohen/the-quest-for-signals-in-react-usestate-on-steroids-71eb9fc87c14)
2221
- [ ] createSignal (https://medium.com/@personal.david.kohen/the-quest-for-signals-in-react-usestate-on-steroids-71eb9fc87c14)
2322
- [ ] useProxyStore (TODO: https://github.com/streamich/react-use/blob/master/src/factory/createGlobalState.ts)

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

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,34 @@ function handler<T>(path: string[]) {
1616
}
1717
}
1818

19+
function updateReference<T extends Record<string, unknown>, C>(currStore: T, storeSlice: Record<string, unknown>, property: string, updatedValue: C) {
20+
if (currStore === storeSlice) {
21+
currStore[property as keyof T] = updatedValue as T[keyof T];
22+
return;
23+
}
24+
for (const prop in currStore) {
25+
if (currStore[prop] === storeSlice) {
26+
if (Array.isArray(currStore[prop])) {
27+
currStore[prop] = [...(currStore[prop] as Array<unknown>)] as T[Extract<keyof T, string>];
28+
} else {
29+
currStore[prop] = { ...(currStore[prop] as Record<string, unknown>) } as T[Extract<keyof T, string>];
30+
}
31+
currStore[prop][property as keyof typeof currStore[typeof prop]] = updatedValue as T[Extract<keyof T, string>][keyof T[typeof prop]];
32+
return;
33+
} else if (Array.isArray(currStore[prop]) || typeof currStore[prop] === "object") {
34+
return updateReference(currStore[prop] as T, storeSlice, property, updatedValue);
35+
}
36+
}
37+
}
38+
1939
/**
2040
* **`createPubSubStore`**: A state management hook implemented on Publish-Subscribe pattern. It allows components to subscribe to state changes and receive updates whenever the state is modified, providing a scalable and decoupled state management solution.
2141
* @param {T extends Record<string, unknown>} obj - Object that rapresent the initialState of the store.
2242
* @param {boolean} [persist=false] - boolean that indicates if the store value will be persisted on the local Storage.
2343
* @returns {{getStore:()=>T, updateStore:(cb:(globStore:T)=>void), usePubSubStore:<C>(subscribe?: (store: T) => C)=>[T|C, (store: T|C|((currStore: T) => T)|((currStore: C) => C)) => void, () => T]}} result
2444
* An object with three _immutable_ functions:
2545
* - __getStore__: function that returns the store object.
26-
* - __updateStore__: function that updates the store: it receives a callback with an only parameter, the store, and return void. Inside this function it can be possible to modify any store properties: changes will be published to every subscribers.
46+
* - __updateStore__: function that updates the store: it receives a callback with an only parameter, the store, and return void. Inside this function it can be possible to modify any store properties: changes will be published to every subscriber.
2747
* - __usePubSubStore__: It's the hook to be used inside components to access the store. It receives an optional callback _subscribe_ to specify to which part of store you want to subscribe.If callback missed, the whole store will be subscribed. It returns an array of three elements:
2848
* - _first element_: the __state__. It represents what has been subscribed.
2949
* - _second element_: the __updateState__. An _immutable_ function that represents the function to update the state. It can be executed given it a new version of the subscribed value or with a callback that receives the subscribed value and returns a new version of it.
@@ -97,25 +117,33 @@ export const createPubSubStore = <T extends Record<string, unknown>>(obj: T, per
97117

98118
const publish = useRef((store: T | C | ((currStore: T) => T) | ((currStore: C) => C)) => {
99119
if (!subscribe) {
100-
const newStore = store instanceof Function ? (store as ((currStore: T) => T))(currentStore.current as T) : store as T;
101-
persist && localStorage.setItem(topicName, JSON.stringify(newStore));
102-
pubSub.publish(topicName, newStore);
120+
//INFO if subscribe is undefined, i am updating the whole store.
121+
const updatedValue = store instanceof Function ? (store as ((currStore: T) => T))(currentStore.current as T) : store as T;
122+
persist && localStorage.setItem(topicName, JSON.stringify(updatedValue));
123+
pubSub.publish(topicName, updatedValue);
103124
} else {
104-
const newStore = store instanceof Function ? (store as ((currStore: C) => C))(currentStore.current as C) : store as C;
105-
let newValue: Record<string, unknown> = {};
106-
if (path.current.length === 1) {
107-
newValue = {...getStore()};
108-
newValue[path.current[path.current.length - 1]] = newStore;
109-
persist && localStorage.setItem(topicName, JSON.stringify(newValue));
110-
pubSub.publish(topicName, newValue);
111-
} else {
112-
for (let i = 0; i < path.current.length - 1; i++) {
113-
!(path.current[i] in newValue) && (newValue[path.current[i]] = getStore()[path.current[i] as keyof ReturnType<typeof getStore>]);
114-
newValue = newValue[path.current[i]] as Record<string, unknown>;
115-
}
116-
newValue[path.current[path.current.length - 1]] = newStore;
125+
/**
126+
* INFO if subscribe provided, i computed updatedValue and i rebuild the path of state defined by subscribe function with path.current variable.
127+
* If updatedValue is an object or an array, i edit the store by variable reference, assigning store value to storeSlice variable if it is empty.
128+
* Otherwise if updatedValue isn't an object or an array, by updateReference function i assign the updatedValue to store directly, searching in its
129+
* properties the reference to storeSlice value.
130+
*/
131+
const updatedValue = store instanceof Function ? (store as ((currStore: C) => C))(currentStore.current as C) : store as C;
132+
let storeSlice: Record<string, unknown> = getStore();
133+
for (let i = 0; i < path.current.length - 1; i++) {
134+
!(path.current[i] in storeSlice) && (storeSlice[path.current[i]] = getStore()[path.current[i] as keyof ReturnType<typeof getStore>]);
135+
storeSlice = storeSlice[path.current[i]] as Record<string, unknown>;
136+
}
137+
if (Array.isArray(updatedValue) || typeof updatedValue === "object") {
138+
storeSlice[path.current[path.current.length - 1]] = updatedValue;
117139
persist && localStorage.setItem(topicName, JSON.stringify(getStore()));
118140
pubSub.publish(topicName, getStore());
141+
} else {
142+
const currStore = getStore();
143+
updateReference(currStore, storeSlice, path.current[path.current.length - 1], updatedValue);
144+
storeSlice = currStore;
145+
persist && localStorage.setItem(topicName, JSON.stringify(storeSlice));
146+
pubSub.publish(topicName, storeSlice);
119147
}
120148
}
121149
});
@@ -128,4 +156,4 @@ export const createPubSubStore = <T extends Record<string, unknown>>(obj: T, per
128156
updateStore,
129157
usePubSubStore
130158
}
131-
}
159+
}

0 commit comments

Comments
 (0)