floppy-disk 2.12.2 → 2.13.0

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 CHANGED
@@ -9,7 +9,7 @@ Both are awesome state manager. That's why this Floppy Disk library behaves like
9
9
 
10
10
  ```js
11
11
  import { create } from 'zustand'; // 3.3 kB (gzipped: 1.5 kB)
12
- import { createStore } from 'floppy-disk'; // 1.4 kB (gzipped: 725 B) 🎉
12
+ import { createStore } from 'floppy-disk'; // 1.4 kB (gzipped: 750 B) 🎉
13
13
 
14
14
  import {
15
15
  QueryClient,
@@ -17,8 +17,8 @@ import {
17
17
  useQuery,
18
18
  useInfiniteQuery,
19
19
  useMutation,
20
- } from '@tanstack/react-query'; // 41 kB (gzipped: 11 kB)
21
- import { createQuery, createMutation } from 'floppy-disk'; // 9.6 kB (gzipped: 3.2 kB) 🎉
20
+ } from '@tanstack/react-query'; // 31.7 kB kB (gzipped: 9.2 kB)
21
+ import { createQuery, createMutation } from 'floppy-disk'; // 9.7 kB (gzipped: 3.3 kB) 🎉
22
22
  ```
23
23
 
24
24
  - Using Zustand & React-Query: https://demo-zustand-react-query.vercel.app/
@@ -30,7 +30,7 @@ import { createQuery, createMutation } from 'floppy-disk'; // 9.6 kB (gzipped: 3
30
30
 
31
31
  - **Create Store**
32
32
  - Get/set store inside/outside component
33
- - Custom reactivity (like `useEffect`'s dependency array)
33
+ - Very simple way to customize the reactivity (state update subscription)
34
34
  - Support middleware
35
35
  - Set state interception
36
36
  - Store event (`onSubscribe`, `onUnsubscribe`, etc.)
@@ -112,12 +112,12 @@ Use the hook anywhere, no providers are needed.
112
112
 
113
113
  ```jsx
114
114
  function Cat() {
115
- const { age } = useCatStore((state) => [state.age]);
115
+ const age = useCatStore('age');
116
116
  return <div>Cat's age: {age}</div>;
117
117
  }
118
118
 
119
119
  function Control() {
120
- const { increaseAge } = useCatStore((state) => [state.increaseAge]);
120
+ const increaseAge = useCatStore('increaseAge');
121
121
  return <button onClick={increaseAge}>Increase cat's age</button>;
122
122
  }
123
123
  ```
@@ -127,29 +127,39 @@ function Control() {
127
127
  Control the reactivity. The concept is same as useEffect dependency array.
128
128
 
129
129
  ```jsx
130
- function Cat() {
130
+ function YourComponent() {
131
131
  const { age, isSleeping } = useCatStore();
132
132
  // Will re-render every state change ^
133
- return <div>...</div>;
134
133
  }
135
134
 
136
- function Cat() {
135
+ function YourComponent() {
137
136
  const { age, isSleeping } = useCatStore((state) => [state.isSleeping]);
138
137
  // Will only re-render when isSleeping is updated ^
139
138
  // Update on age won't cause re-render this component
140
- return <div>...</div>;
141
139
  }
142
140
 
143
- function Cat() {
141
+ function YourComponent() {
144
142
  const { age, isSleeping } = useCatStore((state) => [state.age, state.isSleeping]);
145
143
  // Will re-render when age or isSleeping is updated ^
146
- return <div>...</div>;
147
144
  }
148
145
 
149
- function Cat() {
146
+ function YourComponent() {
150
147
  const { age, isSleeping } = useCatStore((state) => [state.age > 3]);
151
148
  // Will only re-render when (age>3) is updated
152
- return <div>...</div>;
149
+ }
150
+ ```
151
+
152
+ Even simpler way, after version `2.13.0`, we can use store's object key:
153
+
154
+ ```jsx
155
+ function YourComponent() {
156
+ const age = useCatStore('age');
157
+ // Will only re-render when age is updated
158
+ }
159
+
160
+ function YourComponent() {
161
+ const age = useCatStore('isSleeping');
162
+ // Will only re-render when isSleeping is updated
153
163
  }
154
164
  ```
155
165
 
@@ -188,7 +198,7 @@ const decreaseAgeSilently = () => {
188
198
  };
189
199
  // 👇 Will not re-render
190
200
  function Cat() {
191
- const { age } = useCatStore((state) => [state.age]);
201
+ const age = useCatStore('age');
192
202
  return <div>Cat's age: {age}</div>;
193
203
  }
194
204
  ```
@@ -258,7 +268,7 @@ Prevent re-render using `Watch`.
258
268
 
259
269
  ```jsx
260
270
  function CatPage() {
261
- const { age } = useCatStore((state) => [state.age]);
271
+ const age = useCatStore('age');
262
272
  // If age changed, this component will re-render which will cause
263
273
  // HeavyComponent1 & HeavyComponent2 to be re-rendered as well.
264
274
  return (
@@ -276,8 +286,8 @@ function CatPageOptimized() {
276
286
  <main>
277
287
  <HeavyComponent1 />
278
288
  <useCatStore.Watch
279
- selectDeps={(state) => [state.age]}
280
- render={({ age }) => {
289
+ selectDeps="age"
290
+ render={(age) => {
281
291
  return <div>Cat's age: {age}</div>;
282
292
  }}
283
293
  />
@@ -328,11 +338,20 @@ function Parent() {
328
338
 
329
339
  function CatAge() {
330
340
  const { age } = useCatStoreContext()((state) => [state.age]);
341
+
342
+ // Shorthand after v1.13.0:
343
+ // const age = useCatStoreContext()('age');
344
+
331
345
  return <div>Age: {age}</div>;
332
346
  }
347
+
333
348
  function CatIsSleeping() {
334
349
  const useCatStore = useCatStoreContext();
335
350
  const { isSleeping } = useCatStore((state) => [state.isSleeping]);
351
+
352
+ // Shorthand after v1.13.0:
353
+ // const isSleeping = useCatStore('isSleeping');
354
+
336
355
  return (
337
356
  <>
338
357
  <div>Is Sleeping: {String(isSleeping)}</div>
@@ -764,7 +783,7 @@ function SaveProduct() {
764
783
 
765
784
  ## Important Notes
766
785
 
767
- Don't mutate.
786
+ Don't mutate. (unless you use Immer JS library or something similar)
768
787
 
769
788
  ```js
770
789
  import { createStore } from 'floppy-disk';
@@ -783,6 +802,7 @@ Don't use conditional reactivity selector.
783
802
 
784
803
  ```jsx
785
804
  function Cat({ isSomething }) {
805
+ const value = useCatStore(isSomething ? 'age' : 'isSleeping'); // ❌
786
806
  const { age } = useCatStore(isSomething ? (state) => [state.age] : null); // ❌
787
807
  const { age } = useCatStore((state) => (isSomething ? [state.age] : [state.isSleeping])); // ❌
788
808
  return <div>Cat's age: {age}</div>;
@@ -1,5 +1,4 @@
1
1
  import { Maybe } from '../utils';
2
- import { SelectDeps } from '../vanilla';
3
2
  import { CreateQueryOptions, QueryState } from './create-query';
4
3
  import { StoreKey } from './create-stores';
5
4
  export declare const createBiDirectionQuery: <TKey extends StoreKey = StoreKey, TResponse = any, TData extends any[] = any[], TError = unknown, TPageParam = any>(queryFn: (key: TKey, state: QueryState<TKey, TResponse, TData, TError, TPageParam>, direction: 'prev' | 'next') => Promise<TResponse>, options: Omit<CreateQueryOptions<TKey, TResponse, TData, TError, TPageParam>, "select" | "getNextPageParam"> & {
@@ -7,7 +6,7 @@ export declare const createBiDirectionQuery: <TKey extends StoreKey = StoreKey,
7
6
  getNextPageParam: (lastPage: TResponse, index: number, stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError, TPageParam>) => Maybe<TPageParam>;
8
7
  select: (response: TResponse, state: Pick<QueryState<TKey, TResponse, TData, TError, TPageParam>, "data" | "key">, direction: 'prev' | 'next') => TData;
9
8
  }) => {
10
- (...args: [Maybe<TKey>, SelectDeps<QueryState<TKey, TResponse, TData, TError, TPageParam>>?] | [SelectDeps<QueryState<TKey, TResponse, TData, TError, TPageParam>>?]): {
9
+ (key: Maybe<TKey>, selectDeps?: "data" | "key" | "error" | "reset" | "status" | "fetch" | "response" | "keyHash" | "forceFetch" | "fetchNextPage" | "optimisticUpdate" | "isWaiting" | "isWaitingNextPage" | "isRefetching" | "isRefetchError" | "isPreviousData" | "isOptimisticData" | "errorUpdatedAt" | "retryCount" | "isGoingToRetry" | "pageParam" | "pageParams" | "hasNextPage" | "retryNextPageCount" | "isGoingToRetryNextPage" | "isLoading" | "isSuccess" | "isError" | "responseUpdatedAt" | import("..").SelectDeps<QueryState<TKey, TResponse, TData, TError, TPageParam>>): {
11
10
  data: (never[] | TData)[number][];
12
11
  fetchPrevPage: () => Promise<QueryState<TKey, TResponse, TData, TError, TPageParam>>;
13
12
  hasPrevPage: boolean;
@@ -1,19 +1,35 @@
1
1
  import { InitStoreOptions, SelectDeps, SetStoreData, StoreData, StoreInitializer, Subscribers } from '../vanilla';
2
- export type WatchProps<T> = {
3
- selectDeps?: SelectDeps<T>;
4
- render: (state: T) => any;
2
+ export type WatchProps<T, K extends SelectDeps<T> | keyof T = SelectDeps<T>> = {
3
+ selectDeps?: K;
4
+ render: (state: K extends keyof T ? T[K] : T) => any;
5
5
  };
6
6
  export type UseStore<T extends StoreData> = {
7
7
  /**
8
- * @param selectDeps A function that return the dependency array (just like in `useEffect`), to trigger reactivity.
8
+ * @param selectDeps (Optional) A function that return the dependency array (just like in `useEffect`), to trigger reactivity.
9
9
  * Defaults to `undefined` (reactive to all state change) if you didn't set `defaultDeps` on `createStore`.
10
10
  *
11
+ * Since version `2.13.0`, we can use a store's object key to control reactivity.
12
+ *
11
13
  * **IMPORTANT NOTE:** `selectDeps` must not be changed after initialization.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * const useMyStore = createStore({
18
+ * foo: 12,
19
+ * bar: true,
20
+ * baz: "z",
21
+ * });
22
+ *
23
+ * const MyComponent = () => {
24
+ * const foo = useMyStore("foo");
25
+ * // Will only re-render if "foo" updated
26
+ * };
27
+ * ```
12
28
  */
13
- (selectDeps?: SelectDeps<T>): T;
29
+ <K extends SelectDeps<T> | keyof T = SelectDeps<T>>(selectDeps?: K): K extends keyof T ? T[K] : T;
14
30
  get: () => T;
15
31
  set: (value: SetStoreData<T>, silent?: boolean) => void;
16
- subscribe: (fn: (state: T) => void, selectDeps?: SelectDeps<T>) => () => void;
32
+ subscribe: (fn: (state: T) => void, selectDeps?: SelectDeps<T> | keyof T) => () => void;
17
33
  getSubscribers: () => Subscribers<T>;
18
34
  /**
19
35
  * ⚛️ (**_Hook_**)
@@ -25,7 +41,7 @@ export type UseStore<T extends StoreData> = {
25
41
  * - Put this on the root component or parent component, before any component subscribed!
26
42
  */
27
43
  setDefaultValues: (values: SetStoreData<T>) => void;
28
- Watch: (props: WatchProps<T>) => any;
44
+ Watch: <K extends SelectDeps<T> | keyof T = SelectDeps<T>>(props: WatchProps<T, K>) => any;
29
45
  };
30
46
  /**
31
47
  * @see https://floppy-disk.vercel.app/docs/api#createstore
@@ -13,7 +13,7 @@ export const createStore = (initializer, options = {}) => {
13
13
  const [state, setState] = useState(get);
14
14
  // eslint-disable-next-line react-hooks/exhaustive-deps
15
15
  useEffect(() => subscribe(setState, selectDeps), []);
16
- return state;
16
+ return (typeof selectDeps === 'string' ? state[selectDeps] : state);
17
17
  };
18
18
  useStore.get = get;
19
19
  useStore.set = set;
@@ -29,7 +29,7 @@ export const createStore = (initializer, options = {}) => {
29
29
  set(value);
30
30
  });
31
31
  };
32
- const Watch = ({ selectDeps = defaultDeps, render }) => {
32
+ const Watch = ({ selectDeps = defaultDeps, render, }) => {
33
33
  const store = useStore(selectDeps);
34
34
  return render(store);
35
35
  };
@@ -11,19 +11,39 @@ export type StoresInitializer<TKey extends StoreKey = StoreKey, T extends StoreD
11
11
  export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = {
12
12
  /**
13
13
  * @param key (Optional) Store key, an object that will be hashed into a string as a store identifier.
14
+ * No need to memoize the store key.
14
15
  *
15
- * @param selectDeps A function that return the dependency array (just like in `useEffect`), to trigger reactivity.
16
+ * @param selectDeps (Optional) A function that return the dependency array (just like in `useEffect`), to trigger reactivity.
16
17
  * Defaults to `undefined` (reactive to all state change) if you didn't set `defaultDeps` on `createStores`.
17
18
  *
19
+ * Since version `2.13.0`, we can use a store's object key to control reactivity.
20
+ *
18
21
  * **IMPORTANT NOTE:** `selectDeps` must not be changed after initialization.
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * type StoreKey = { id: string };
26
+ * type StoreData = { foo: number; bar: boolean; baz: string };
27
+ * const useMyStores = createStores<StoreKey, StoreData>({
28
+ * foo: 12,
29
+ * bar: true,
30
+ * baz: "z",
31
+ * });
32
+ *
33
+ * export const MyComponent = () => {
34
+ * const foo = useMyStores({ id: "p1" }, "foo");
35
+ * // Will only re-render if "foo" & store key ("id") updated
36
+ * };
37
+ * ```
19
38
  */
20
- (...args: [Maybe<TKey>, SelectDeps<T>?] | [SelectDeps<T>?]): T;
39
+ <K extends SelectDeps<T> | keyof T = SelectDeps<T>>(selectDeps?: K): K extends keyof T ? T[K] : T;
40
+ <K extends SelectDeps<T> | keyof T = SelectDeps<T>>(key: Maybe<TKey>, selectDeps?: K): K extends keyof T ? T[K] : T;
21
41
  get: (key?: Maybe<TKey>) => T;
22
42
  getAll: () => T[];
23
43
  getAllWithSubscriber: () => T[];
24
44
  set: (key: Maybe<TKey>, value: SetStoreData<T>, silent?: boolean) => void;
25
45
  setAll: (value: SetStoreData<T>, silent?: boolean) => void;
26
- subscribe: (key: Maybe<TKey>, fn: (state: T) => void, selectDeps?: SelectDeps<T>) => () => void;
46
+ subscribe: (key: Maybe<TKey>, fn: (state: T) => void, selectDeps?: SelectDeps<T> | keyof T) => () => void;
27
47
  getSubscribers: (key: Maybe<TKey>) => Subscribers<T>;
28
48
  getStore: (key?: Maybe<TKey>) => InitStoreReturn<T>;
29
49
  getStores: () => Map<string, InitStoreReturn<T>>;
@@ -37,7 +57,7 @@ export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = St
37
57
  * - Put this on the root component or parent component, before any component subscribed!
38
58
  */
39
59
  setDefaultValues: (key: Maybe<TKey>, values: SetStoreData<T>) => void;
40
- Watch: (props: WatchProps<T> & {
60
+ Watch: <K extends SelectDeps<T> | keyof T = SelectDeps<T>>(props: WatchProps<T, K> & {
41
61
  storeKey?: Maybe<TKey>;
42
62
  }) => any;
43
63
  };
@@ -20,7 +20,7 @@ export const createStores = (initializer, options = {}) => {
20
20
  * **IMPORTANT NOTE:** `selectDeps` function must not be changed after initialization.
21
21
  */
22
22
  const useStores = (...args) => {
23
- const [_key, selectDeps = defaultDeps] = (typeof args[0] === 'function' ? [{}, args[0]] : args);
23
+ const [_key, selectDeps = defaultDeps] = (typeof args[0] === 'function' || typeof args[0] === 'string' ? [{}, args[0]] : args);
24
24
  const key = _key || {};
25
25
  const keyHash = hashKeyFn(key);
26
26
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -37,7 +37,8 @@ export const createStores = (initializer, options = {}) => {
37
37
  }, [keyHash]);
38
38
  if (keyHash !== prevKeyHash.current)
39
39
  onBeforeChangeKey(key, prevKey.current);
40
- return get();
40
+ const state = get();
41
+ return (typeof selectDeps === 'string' ? state[selectDeps] : state);
41
42
  };
42
43
  useStores.get = (key) => {
43
44
  const store = getStore(key);
package/esm/vanilla.d.ts CHANGED
@@ -18,7 +18,7 @@ export type InitStoreOptions<T> = {
18
18
  export type InitStoreReturn<T> = {
19
19
  get: () => T;
20
20
  set: (value: SetStoreData<T>, silent?: boolean) => void;
21
- subscribe: (fn: (state: T) => void, selectDeps?: SelectDeps<T>) => () => void;
21
+ subscribe: (fn: (state: T) => void, selectDeps?: SelectDeps<T> | keyof T) => () => void;
22
22
  getSubscribers: () => Subscribers<T>;
23
23
  };
24
24
  export declare const initStore: <T extends StoreData>(initializer: StoreInitializer<T>, options?: InitStoreOptions<T>) => InitStoreReturn<T>;
package/esm/vanilla.js CHANGED
@@ -35,7 +35,7 @@ export const initStore = (initializer, options = {}) => {
35
35
  });
36
36
  };
37
37
  const subscribe = (fn, selectDeps) => {
38
- subscribers.set(fn, selectDeps);
38
+ subscribers.set(fn, (typeof selectDeps === 'string' ? (s) => [s[selectDeps]] : selectDeps));
39
39
  if (subscribers.size === 1)
40
40
  onFirstSubscribe(data);
41
41
  onSubscribe(data);
@@ -1,5 +1,4 @@
1
1
  import { Maybe } from '../utils';
2
- import { SelectDeps } from '../vanilla';
3
2
  import { CreateQueryOptions, QueryState } from './create-query';
4
3
  import { StoreKey } from './create-stores';
5
4
  export declare const createBiDirectionQuery: <TKey extends StoreKey = StoreKey, TResponse = any, TData extends any[] = any[], TError = unknown, TPageParam = any>(queryFn: (key: TKey, state: QueryState<TKey, TResponse, TData, TError, TPageParam>, direction: 'prev' | 'next') => Promise<TResponse>, options: Omit<CreateQueryOptions<TKey, TResponse, TData, TError, TPageParam>, "select" | "getNextPageParam"> & {
@@ -7,7 +6,7 @@ export declare const createBiDirectionQuery: <TKey extends StoreKey = StoreKey,
7
6
  getNextPageParam: (lastPage: TResponse, index: number, stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError, TPageParam>) => Maybe<TPageParam>;
8
7
  select: (response: TResponse, state: Pick<QueryState<TKey, TResponse, TData, TError, TPageParam>, "data" | "key">, direction: 'prev' | 'next') => TData;
9
8
  }) => {
10
- (...args: [Maybe<TKey>, SelectDeps<QueryState<TKey, TResponse, TData, TError, TPageParam>>?] | [SelectDeps<QueryState<TKey, TResponse, TData, TError, TPageParam>>?]): {
9
+ (key: Maybe<TKey>, selectDeps?: "data" | "key" | "error" | "reset" | "status" | "fetch" | "response" | "keyHash" | "forceFetch" | "fetchNextPage" | "optimisticUpdate" | "isWaiting" | "isWaitingNextPage" | "isRefetching" | "isRefetchError" | "isPreviousData" | "isOptimisticData" | "errorUpdatedAt" | "retryCount" | "isGoingToRetry" | "pageParam" | "pageParams" | "hasNextPage" | "retryNextPageCount" | "isGoingToRetryNextPage" | "isLoading" | "isSuccess" | "isError" | "responseUpdatedAt" | import("..").SelectDeps<QueryState<TKey, TResponse, TData, TError, TPageParam>>): {
11
10
  data: (never[] | TData)[number][];
12
11
  fetchPrevPage: () => Promise<QueryState<TKey, TResponse, TData, TError, TPageParam>>;
13
12
  hasPrevPage: boolean;
@@ -1,19 +1,35 @@
1
1
  import { InitStoreOptions, SelectDeps, SetStoreData, StoreData, StoreInitializer, Subscribers } from '../vanilla';
2
- export type WatchProps<T> = {
3
- selectDeps?: SelectDeps<T>;
4
- render: (state: T) => any;
2
+ export type WatchProps<T, K extends SelectDeps<T> | keyof T = SelectDeps<T>> = {
3
+ selectDeps?: K;
4
+ render: (state: K extends keyof T ? T[K] : T) => any;
5
5
  };
6
6
  export type UseStore<T extends StoreData> = {
7
7
  /**
8
- * @param selectDeps A function that return the dependency array (just like in `useEffect`), to trigger reactivity.
8
+ * @param selectDeps (Optional) A function that return the dependency array (just like in `useEffect`), to trigger reactivity.
9
9
  * Defaults to `undefined` (reactive to all state change) if you didn't set `defaultDeps` on `createStore`.
10
10
  *
11
+ * Since version `2.13.0`, we can use a store's object key to control reactivity.
12
+ *
11
13
  * **IMPORTANT NOTE:** `selectDeps` must not be changed after initialization.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * const useMyStore = createStore({
18
+ * foo: 12,
19
+ * bar: true,
20
+ * baz: "z",
21
+ * });
22
+ *
23
+ * const MyComponent = () => {
24
+ * const foo = useMyStore("foo");
25
+ * // Will only re-render if "foo" updated
26
+ * };
27
+ * ```
12
28
  */
13
- (selectDeps?: SelectDeps<T>): T;
29
+ <K extends SelectDeps<T> | keyof T = SelectDeps<T>>(selectDeps?: K): K extends keyof T ? T[K] : T;
14
30
  get: () => T;
15
31
  set: (value: SetStoreData<T>, silent?: boolean) => void;
16
- subscribe: (fn: (state: T) => void, selectDeps?: SelectDeps<T>) => () => void;
32
+ subscribe: (fn: (state: T) => void, selectDeps?: SelectDeps<T> | keyof T) => () => void;
17
33
  getSubscribers: () => Subscribers<T>;
18
34
  /**
19
35
  * ⚛️ (**_Hook_**)
@@ -25,7 +41,7 @@ export type UseStore<T extends StoreData> = {
25
41
  * - Put this on the root component or parent component, before any component subscribed!
26
42
  */
27
43
  setDefaultValues: (values: SetStoreData<T>) => void;
28
- Watch: (props: WatchProps<T>) => any;
44
+ Watch: <K extends SelectDeps<T> | keyof T = SelectDeps<T>>(props: WatchProps<T, K>) => any;
29
45
  };
30
46
  /**
31
47
  * @see https://floppy-disk.vercel.app/docs/api#createstore
@@ -16,7 +16,7 @@ const createStore = (initializer, options = {}) => {
16
16
  const [state, setState] = (0, react_1.useState)(get);
17
17
  // eslint-disable-next-line react-hooks/exhaustive-deps
18
18
  (0, react_1.useEffect)(() => subscribe(setState, selectDeps), []);
19
- return state;
19
+ return (typeof selectDeps === 'string' ? state[selectDeps] : state);
20
20
  };
21
21
  useStore.get = get;
22
22
  useStore.set = set;
@@ -32,7 +32,7 @@ const createStore = (initializer, options = {}) => {
32
32
  set(value);
33
33
  });
34
34
  };
35
- const Watch = ({ selectDeps = defaultDeps, render }) => {
35
+ const Watch = ({ selectDeps = defaultDeps, render, }) => {
36
36
  const store = useStore(selectDeps);
37
37
  return render(store);
38
38
  };
@@ -11,19 +11,39 @@ export type StoresInitializer<TKey extends StoreKey = StoreKey, T extends StoreD
11
11
  export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = {
12
12
  /**
13
13
  * @param key (Optional) Store key, an object that will be hashed into a string as a store identifier.
14
+ * No need to memoize the store key.
14
15
  *
15
- * @param selectDeps A function that return the dependency array (just like in `useEffect`), to trigger reactivity.
16
+ * @param selectDeps (Optional) A function that return the dependency array (just like in `useEffect`), to trigger reactivity.
16
17
  * Defaults to `undefined` (reactive to all state change) if you didn't set `defaultDeps` on `createStores`.
17
18
  *
19
+ * Since version `2.13.0`, we can use a store's object key to control reactivity.
20
+ *
18
21
  * **IMPORTANT NOTE:** `selectDeps` must not be changed after initialization.
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * type StoreKey = { id: string };
26
+ * type StoreData = { foo: number; bar: boolean; baz: string };
27
+ * const useMyStores = createStores<StoreKey, StoreData>({
28
+ * foo: 12,
29
+ * bar: true,
30
+ * baz: "z",
31
+ * });
32
+ *
33
+ * export const MyComponent = () => {
34
+ * const foo = useMyStores({ id: "p1" }, "foo");
35
+ * // Will only re-render if "foo" & store key ("id") updated
36
+ * };
37
+ * ```
19
38
  */
20
- (...args: [Maybe<TKey>, SelectDeps<T>?] | [SelectDeps<T>?]): T;
39
+ <K extends SelectDeps<T> | keyof T = SelectDeps<T>>(selectDeps?: K): K extends keyof T ? T[K] : T;
40
+ <K extends SelectDeps<T> | keyof T = SelectDeps<T>>(key: Maybe<TKey>, selectDeps?: K): K extends keyof T ? T[K] : T;
21
41
  get: (key?: Maybe<TKey>) => T;
22
42
  getAll: () => T[];
23
43
  getAllWithSubscriber: () => T[];
24
44
  set: (key: Maybe<TKey>, value: SetStoreData<T>, silent?: boolean) => void;
25
45
  setAll: (value: SetStoreData<T>, silent?: boolean) => void;
26
- subscribe: (key: Maybe<TKey>, fn: (state: T) => void, selectDeps?: SelectDeps<T>) => () => void;
46
+ subscribe: (key: Maybe<TKey>, fn: (state: T) => void, selectDeps?: SelectDeps<T> | keyof T) => () => void;
27
47
  getSubscribers: (key: Maybe<TKey>) => Subscribers<T>;
28
48
  getStore: (key?: Maybe<TKey>) => InitStoreReturn<T>;
29
49
  getStores: () => Map<string, InitStoreReturn<T>>;
@@ -37,7 +57,7 @@ export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = St
37
57
  * - Put this on the root component or parent component, before any component subscribed!
38
58
  */
39
59
  setDefaultValues: (key: Maybe<TKey>, values: SetStoreData<T>) => void;
40
- Watch: (props: WatchProps<T> & {
60
+ Watch: <K extends SelectDeps<T> | keyof T = SelectDeps<T>>(props: WatchProps<T, K> & {
41
61
  storeKey?: Maybe<TKey>;
42
62
  }) => any;
43
63
  };
@@ -23,7 +23,7 @@ const createStores = (initializer, options = {}) => {
23
23
  * **IMPORTANT NOTE:** `selectDeps` function must not be changed after initialization.
24
24
  */
25
25
  const useStores = (...args) => {
26
- const [_key, selectDeps = defaultDeps] = (typeof args[0] === 'function' ? [{}, args[0]] : args);
26
+ const [_key, selectDeps = defaultDeps] = (typeof args[0] === 'function' || typeof args[0] === 'string' ? [{}, args[0]] : args);
27
27
  const key = _key || {};
28
28
  const keyHash = hashKeyFn(key);
29
29
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -40,7 +40,8 @@ const createStores = (initializer, options = {}) => {
40
40
  }, [keyHash]);
41
41
  if (keyHash !== prevKeyHash.current)
42
42
  onBeforeChangeKey(key, prevKey.current);
43
- return get();
43
+ const state = get();
44
+ return (typeof selectDeps === 'string' ? state[selectDeps] : state);
44
45
  };
45
46
  useStores.get = (key) => {
46
47
  const store = getStore(key);
package/lib/vanilla.d.ts CHANGED
@@ -18,7 +18,7 @@ export type InitStoreOptions<T> = {
18
18
  export type InitStoreReturn<T> = {
19
19
  get: () => T;
20
20
  set: (value: SetStoreData<T>, silent?: boolean) => void;
21
- subscribe: (fn: (state: T) => void, selectDeps?: SelectDeps<T>) => () => void;
21
+ subscribe: (fn: (state: T) => void, selectDeps?: SelectDeps<T> | keyof T) => () => void;
22
22
  getSubscribers: () => Subscribers<T>;
23
23
  };
24
24
  export declare const initStore: <T extends StoreData>(initializer: StoreInitializer<T>, options?: InitStoreOptions<T>) => InitStoreReturn<T>;
package/lib/vanilla.js CHANGED
@@ -38,7 +38,7 @@ const initStore = (initializer, options = {}) => {
38
38
  });
39
39
  };
40
40
  const subscribe = (fn, selectDeps) => {
41
- subscribers.set(fn, selectDeps);
41
+ subscribers.set(fn, (typeof selectDeps === 'string' ? (s) => [s[selectDeps]] : selectDeps));
42
42
  if (subscribers.size === 1)
43
43
  onFirstSubscribe(data);
44
44
  onSubscribe(data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "floppy-disk",
3
- "version": "2.12.2",
3
+ "version": "2.13.0",
4
4
  "description": "FloppyDisk - lightweight, simple, and powerful state management library",
5
5
  "keywords": [
6
6
  "state",