Skip to content

Commit 5175cbf

Browse files
committed
[UPD] README + [FIX] improvements
1 parent 6a530e1 commit 5175cbf

10 files changed

Lines changed: 140 additions & 67 deletions

File tree

apps/react-tools-demo/src/components/hooks/createPubSubStore/CreatePubSubStore.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,22 @@ In this example it has been used _createPubSubStore_ hook to create a global sto
1010
- _updateStore_ hook to update _spinner_ property from __store__ when button _handle spinner_ is clicked.
1111
*/
1212
//File store.ts
13-
const store = createPubSubStore({user: { id: 0, name: "", eta: 0}, spinner: false});
14-
export const { usePubSubStore, updateStore } = store;
13+
const store = createPubSubStore(
14+
{
15+
user: {
16+
id: 0,
17+
name: "",
18+
eta: 0
19+
},
20+
spinner: false
21+
},
22+
{
23+
toggleSpinner: (store, val: boolean) => {
24+
store.spinner = val;
25+
}
26+
}
27+
);
28+
export const { usePubSubStore, mutateStore } = store;
1529

1630
//import {usePubSubStore} from '../store.ts';
1731
const Comp1 = () => {
@@ -27,17 +41,17 @@ const Comp1 = () => {
2741
</div>
2842
}
2943

30-
//import {updateStore, usePubSubStore} from '../store.ts';
44+
//import {usePubSubStore} from '../store.ts';
3145
const Comp2 = memo(() => {
32-
const [state, setState] = usePubSubStore(store => store.user.name);
46+
const [state, setState, , mutators] = usePubSubStore(store => store.user.name);
3347

3448
return <div>
3549
<label htmlFor="name">NAME:</label>
3650
<input type="text" name="name" value={state} onChange={(e) => setState(e.target.value)} />
3751
<button onClick={async () => {
38-
updateStore(store => store.spinner = true);
52+
mutators.toggleSpinner(true);
3953
await new Promise(res => setTimeout(res, 4000));
40-
updateStore(store => store.spinner = false);
54+
mutators.toggleSpinner(false);
4155
}}>Enable Spinner</button>
4256
</div>
4357
})

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

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,34 @@
11
# createPubSubStore
2-
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.
2+
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.__N.B.: to work properly, objects like Set, Map, Date or more generally objects without _Symbol.iterator_ must be treated as immutable__.
33

44
## Usage
55

66
```tsx
77
//File store.ts
8-
const store = createPubSubStore({user: { id: 0, name: "", eta: 0}, spinner: false});
9-
export const { usePubSubStore, updateStore } = store;
8+
const store = createPubSubStore(
9+
{
10+
user: {
11+
id: 0,
12+
name: "",
13+
eta: 0
14+
},
15+
counter: 0,
16+
spinner: false
17+
},
18+
{
19+
toggleSpinner: (store, val: boolean) => {
20+
store.spinner = val;
21+
}
22+
}
23+
);
24+
export const { usePubSubStore, mutateStore } = store;
1025

1126
//import {usePubSubStore} from '../store.ts';
1227
const Comp1 = () => {
1328
const [state, setState] = usePubSubStore(store => store.user);
29+
useEffect(() => {
30+
console.log(state);
31+
}, [state])
1432

1533
return <div>
1634
<label htmlFor="id">ID:</label>
@@ -22,27 +40,33 @@ const Comp1 = () => {
2240
</div>
2341
}
2442

25-
//import {updateStore, usePubSubStore} from '../store.ts';
43+
//import {usePubSubStore} from '../store.ts';
2644
const Comp2 = memo(() => {
27-
const [state, setState] = usePubSubStore(store => store.user.name);
45+
const [state, setState, , mutators] = usePubSubStore(store => store.user.name);
2846

2947
return <div>
3048
<label htmlFor="name">NAME:</label>
3149
<input type="text" name="name" value={state} onChange={(e) => setState(e.target.value)} />
3250
<button onClick={async () => {
33-
updateStore(store => store.spinner = true);
51+
mutators.toggleSpinner(true);
3452
await new Promise(res => setTimeout(res, 4000));
35-
updateStore(store => store.spinner = false);
53+
mutators.toggleSpinner(false);
3654
}}>Enable Spinner</button>
3755
</div>
3856
})
3957

4058
//import {usePubSubStore} from '../store.ts';
4159
export const CreatePubSubStore = () => {
4260
const [spinner] = usePubSubStore(store => store.spinner);
61+
const [counter, setCounter] = usePubSubStore(store => store.counter);
62+
useEffect(() => {
63+
const id = setInterval(() => setCounter(counter => counter++), 1000);
64+
return () => clearInterval(id);
65+
}, [setCounter])
4366
return <div style={{ display: "grid", gridTemplateRows: "auto auto", gap: 20, justifyContent: "center" }}>
4467
<fieldset style={{padding: 20}}>
4568
<legend>Component 2</legend>
69+
<p>{counter}</p>
4670
<Comp2 />
4771
</fieldset>
4872
{
@@ -67,13 +91,15 @@ export const CreatePubSubStore = () => {
6791
## API
6892

6993
```tsx
70-
createPubSubStore<T extends Record<string, unknown>>(obj: T, persist?: boolean): { getStore: () => T; updateStore: (cb: (globStore: T) => void) => void; usePubSubStore: { (subscribe?: undefined): [T, (store: T | ((currStore: T) => T)) => void, () => T]; <C>(subscribe?: (store: T) => C): [C, (store: C | ((currStore: C) => C)) => void, () => C]; <C>(subscribe?: (store: T) => C):[T | C, (store: T | C | ((currStore: T) => T) | ((currStore: C) => C)) => void, () => T] }}
94+
createPubSubStore<T extends Record<string, unknown>, E extends Record<string, (store: T, ...args: any) => void>>(obj: T, mutatorsFn?: E, persist?: boolean): { getStore: () => T; mutateStore: (cb: (globStore: T) => void) => void; mutators: Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>) => void>, usePubSubStore: { (subscribe?: undefined): [T, (store: T | ((currStore: T) => T)) => void, () => T, Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>) => void>]; <C>(subscribe?: (store: T) => C): [C, (store: C | ((currStore: C) => C)) => void, () => C, Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>) => void>]; <C>(subscribe?: (store: T) => C): [T | C, (store: T | C | ((currStore: T) => T) | ((currStore: C) => C)) => void, () => T, Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>) => void>] }}
7195
```
7296
7397
> ### Params
7498
>
7599
> - __obj__: _T extends Record<string, unknown>_
76100
Object that rapresent the initialState of the store.
101+
> - __mutatorsFn?__: _E extends Record<string, (store: T, ...args: any) => void>_
102+
Object that contains specified void function to mutate the store value, not the store itself, that receives the store as first parameter and other optional parameters.
77103
> - __persist=false?__: _boolean_
78104
boolean that indicates if the store value will be persisted on the local Storage.
79105
>
@@ -82,13 +108,15 @@ boolean that indicates if the store value will be persisted on the local Storage
82108
>
83109
> __result__: __Object__:
84110
- __getStore__ : _()=>T_
85-
- __updateStore__ : _(cb:(globStore:T)=>void)_
111+
- __mutateStore__ : _(cb:(globStore:T)=>void)_
86112
- __usePubSubStore__ : _<C>(subscribe?: (store: T) => C)=>[T|C, (store: T|C|((currStore: T) => T)|((currStore: C) => C)) => void, () => T]_
87-
> An object with three _immutable_ functions:
88-
> - __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 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:
113+
> An object with:
114+
> - __getStore__: __IMMUTABLE__ function that returns the store object.
115+
> - __mutateStore__: __IMMUTABLE__ function that modifies the store value, not the store itself, by a void callback function that receives an only parameter, the store. Changes will be published to every subscriber.
116+
> - __mutators__: object with __IMMUTABLE__ functions built on _mutatorsFn_ param, if it is present: they work like __mutateStore__ function and they can be executed passing them optional parameters if specified in _mutatorsFn param_. Changes will be published to every subscriber.
117+
> - __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 four elements:
91118
> - _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.
119+
> - _second element_: the __setState__. An _immutable_ 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.
93120
> - _third element_: the __getState__. An _immutable_ function that returns the current subscribed value.
121+
> - _fourth element_: the __mutators__. Like above.
94122
>

packages/react-tools/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
- [x] useDerivedState
1818
- [x] useStateValidator
1919
- [x] createPubSubStore
20-
- [ ] useSignal (https://medium.com/@personal.david.kohen/the-quest-for-signals-in-react-usestate-on-steroids-71eb9fc87c14)
21-
- [ ] createSignal (https://medium.com/@personal.david.kohen/the-quest-for-signals-in-react-usestate-on-steroids-71eb9fc87c14)
22-
- [ ] useProxyStore (TODO: https://github.com/streamich/react-use/blob/master/src/factory/createGlobalState.ts)
23-
- [ ] createProxyStore (TODO: https://github.com/streamich/react-use/blob/master/src/factory/createGlobalState.ts)
20+
- [ ] useSignal (TODO: like useProxyStore)
21+
- [ ] createSignal (TODO: like createProxyStore)
22+
- [ ] useProxyStore (TODO: proxy based)
23+
- [ ] createProxyStore (TODO: proxy based)
2424

2525
- __LIFECYCLE__
2626
- [x] useEffectCompare
@@ -123,8 +123,8 @@
123123
- [x] useFetch
124124
- [x] useLock
125125
- [x] useBroadcastChannel
126-
- [ ] useIndexedDB (TODO: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API?retiredLocale=it)
127-
- [ ] useIdleDetection (not work yet. https://developer.mozilla.org/en-US/docs/Web/API/Idle_Detection_API)
126+
- [ ] useIndexedDB (TODO: refer to api)
127+
- [ ] useIdleDetection (TODO: not work yet)
128128

129129
- __UTILS__
130130
- [x] isShallowEqual

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

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
12
import { useCallback, useRef } from "react";
23
import { useLazyRef, useSyncExternalStore } from ".";
34
import { PublishSubscribePattern, isDeepEqual } from "../utils";
5+
import { ExtractTail } from "../models";
46

57
let id = 0;
68
function handler<T>(path: string[]) {
79
return {
810
get(target: T, prop: string):unknown {
911
path.push(prop as string);
1012
const val = Reflect.get(target as object, prop);
11-
if (val instanceof Date || val instanceof RegExp || val instanceof Map || val instanceof Set || typeof val !== "object") {
13+
if (val === null || val === undefined || val instanceof Blob || val instanceof Date || val instanceof RegExp || val instanceof Map || val instanceof Set || typeof val !== "object") {
1214
return val;
1315
}
1416
return new Proxy(val, handler(path));
@@ -37,22 +39,28 @@ function updateReference<T extends Record<string, unknown>, C>(currStore: T, sto
3739
}
3840

3941
/**
40-
* **`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.
42+
* **`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.__N.B.: to work properly, objects like Set, Map, Date or more generally objects without _Symbol.iterator_ must be treated as immutable__.
4143
* @param {T extends Record<string, unknown>} obj - Object that rapresent the initialState of the store.
44+
* @param {E extends Record<string, (store: T, ...args: any) => void>} [mutatorsFn] - Object that contains specified void function to mutate the store value, not the store itself, that receives the store as first parameter and other optional parameters.
4245
* @param {boolean} [persist=false] - boolean that indicates if the store value will be persisted on the local Storage.
43-
* @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
44-
* An object with three _immutable_ functions:
45-
* - __getStore__: function that returns the store object.
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.
47-
* - __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:
46+
* @returns {{getStore:()=>T, mutateStore:(cb:(globStore:T)=>void), usePubSubStore:<C>(subscribe?: (store: T) => C)=>[T|C, (store: T|C|((currStore: T) => T)|((currStore: C) => C)) => void, () => T]}} result
47+
* An object with:
48+
* - __getStore__: __IMMUTABLE__ function that returns the store object.
49+
* - __mutateStore__: __IMMUTABLE__ function that modifies the store value, not the store itself, by a void callback function that receives an only parameter, the store. Changes will be published to every subscriber.
50+
* - __mutators__: object with __IMMUTABLE__ functions built on _mutatorsFn_ param, if it is present: they work like __mutateStore__ function and they can be executed passing them optional parameters if specified in _mutatorsFn param_. Changes will be published to every subscriber.
51+
* - __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 four elements:
4852
* - _first element_: the __state__. It represents what has been subscribed.
49-
* - _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.
53+
* - _second element_: the __setState__. An _immutable_ 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.
5054
* - _third element_: the __getState__. An _immutable_ function that returns the current subscribed value.
55+
* - _fourth element_: the __mutators__. Like above.
5156
*/
52-
export const createPubSubStore = <T extends Record<string, unknown>>(obj: T, persist?: boolean): { getStore: () => T; updateStore: (cb: (globStore: T) => void) => void; usePubSubStore: { (subscribe?: undefined): [T, (store: T | ((currStore: T) => T)) => void, () => T]; <C>(subscribe?: (store: T) => C): [C, (store: C | ((currStore: C) => C)) => void, () => C]; <C>(subscribe?: (store: T) => C):[T | C, (store: T | C | ((currStore: T) => T) | ((currStore: C) => C)) => void, () => T] }} => {
57+
export const createPubSubStore = <T extends Record<string, unknown>, E extends Record<string, (store: T, ...args: any) => void>>(obj: T, mutatorsFn?: E, persist?: boolean): { getStore: () => T; mutateStore: (cb: (globStore: T) => void) => void; mutators: Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>) => void>, usePubSubStore: { (subscribe?: undefined): [T, (store: T | ((currStore: T) => T)) => void, () => T, Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>) => void>]; <C>(subscribe?: (store: T) => C): [C, (store: C | ((currStore: C) => C)) => void, () => C, Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>) => void>]; <C>(subscribe?: (store: T) => C): [T | C, (store: T | C | ((currStore: T) => T) | ((currStore: C) => C)) => void, () => T, Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>) => void>] }} => {
5358
const pubSub = new PublishSubscribePattern();
5459
const topicName = "pub_Sub_str-" + id;
60+
id++;
5561
let store:T;
62+
const mutators: Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>) => void> = {} as Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>) => void>;
63+
5664
if (persist) {
5765
const serializedStore = localStorage.getItem(topicName);
5866
if (serializedStore) {
@@ -65,7 +73,17 @@ export const createPubSubStore = <T extends Record<string, unknown>>(obj: T, per
6573
store = { ...obj };
6674
localStorage.setItem(topicName, JSON.stringify(store));
6775
}
68-
id++;
76+
77+
if (mutatorsFn) {
78+
for (const key in mutatorsFn) {
79+
mutators[key] = (...args) => {
80+
const str = getStore();
81+
mutatorsFn[key](str, ...args);
82+
persist && localStorage.setItem(topicName, JSON.stringify(str));
83+
pubSub.publish(topicName, str);
84+
}
85+
}
86+
}
6987
pubSub.subscribe(topicName, (val?: T) => {
7088
store = val!
7189
});
@@ -74,16 +92,16 @@ export const createPubSubStore = <T extends Record<string, unknown>>(obj: T, per
7492
return store;
7593
}
7694

77-
function updateStore(cb: (globStore: T) => void) {
95+
function mutateStore(cb: (globStore: T) => void) {
7896
const globStore = getStore();
7997
cb(globStore);
8098
persist && localStorage.setItem(topicName, JSON.stringify(globStore));
8199
pubSub.publish(topicName, globStore);
82100
}
83101

84-
function usePubSubStore(subscribe?: undefined): [T, (store: T | ((currStore: T) => T)) => void, () => T];
85-
function usePubSubStore<C>(subscribe?: (store: T) => C): [C, (store: C | ((currStore: C) => C)) => void, () => C];
86-
function usePubSubStore<C>(subscribe?: (store: T) => C): [T | C, (store: T | C | ((currStore: T) => T) | ((currStore: C) => C)) => void, () => T | C] {
102+
function usePubSubStore(subscribe?: undefined): [T, (store: T | ((currStore: T) => T)) => void, () => T, Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>)=>void>];
103+
function usePubSubStore<C>(subscribe?: (store: T) => C): [C, (store: C | ((currStore: C) => C)) => void, () => C, Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>)=>void>];
104+
function usePubSubStore<C>(subscribe?: (store: T) => C): [T | C, (store: T | C | ((currStore: T) => T) | ((currStore: C) => C)) => void, () => T | C, Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>)=>void>] {
87105
const path = useRef<string[]>([]);
88106
const currentStore = useLazyRef<T | C>(() => {
89107
if (!subscribe) {
@@ -115,7 +133,7 @@ export const createPubSubStore = <T extends Record<string, unknown>>(obj: T, per
115133

116134
const getState = useRef(() => currentStore.current);
117135

118-
const publish = useRef((store: T | C | ((currStore: T) => T) | ((currStore: C) => C)) => {
136+
const setState = useRef((store: T | C | ((currStore: T) => T) | ((currStore: C) => C)) => {
119137
if (!subscribe) {
120138
//INFO if subscribe is undefined, i am updating the whole store.
121139
const updatedValue = store instanceof Function ? (store as ((currStore: T) => T))(currentStore.current as T) : store as T;
@@ -148,12 +166,13 @@ export const createPubSubStore = <T extends Record<string, unknown>>(obj: T, per
148166
}
149167
});
150168

151-
return [state, publish.current, getState.current];
169+
return [state, setState.current, getState.current, mutators];
152170
}
153171

154172
return {
155173
getStore,
156-
updateStore,
174+
mutateStore,
175+
mutators,
157176
usePubSubStore
158177
}
159178
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ export const usePerformAction = <T extends (...args: unknown[]) => void>(cb: T):
1414

1515
useEffect(() => {
1616
if (deferredValue !== 0) {
17-
actionRef.current();
17+
argsActionRef.current
18+
? actionRef.current(...argsActionRef.current)
19+
: actionRef.current();
1820
}
1921
}, [deferredValue])
2022

0 commit comments

Comments
 (0)