juststore 0.3.5 → 0.4.1

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/dist/form.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  'use client';
3
3
  import { pascalCase } from 'change-case';
4
- import { useId } from 'react';
4
+ import { useEffect, useId, useMemo } from 'react';
5
5
  import { getSnapshot, produce } from './impl';
6
6
  import { createNode } from './node';
7
7
  import { createStoreRoot } from './root';
@@ -36,7 +36,7 @@ function useForm(defaultValue, fieldConfigs = {}) {
36
36
  const errorNamespace = `errors.${namespace}`;
37
37
  const storeApi = createStoreRoot(namespace, defaultValue, { memoryOnly: true });
38
38
  const errorStore = createStoreRoot(errorNamespace, {}, { memoryOnly: true });
39
- const formStore = {
39
+ const formStore = useMemo(() => ({
40
40
  clearErrors: () => produce(errorNamespace, undefined, false, true),
41
41
  handleSubmit: (onSubmit) => (e) => {
42
42
  e.preventDefault();
@@ -45,8 +45,8 @@ function useForm(defaultValue, fieldConfigs = {}) {
45
45
  onSubmit(getSnapshot(namespace));
46
46
  }
47
47
  }
48
- };
49
- const store = new Proxy(storeApi, {
48
+ }), [namespace, errorNamespace]);
49
+ const store = useMemo(() => new Proxy(storeApi, {
50
50
  get(_target, prop) {
51
51
  if (prop in formStore) {
52
52
  return formStore[prop];
@@ -59,22 +59,34 @@ function useForm(defaultValue, fieldConfigs = {}) {
59
59
  }
60
60
  return undefined;
61
61
  }
62
- });
63
- for (const entry of Object.entries(fieldConfigs)) {
64
- const [path, config] = entry;
65
- const validator = getValidator(path, config?.validate);
66
- if (validator) {
67
- storeApi.subscribe(path, (value) => {
68
- const error = validator(value, store);
69
- if (!error) {
70
- errorStore.reset(path);
71
- }
72
- else {
73
- errorStore.set(path, error);
74
- }
75
- });
62
+ }), [storeApi, formStore, errorStore]);
63
+ const unsubscribeFns = useMemo(() => {
64
+ const unsubscribeFns = [];
65
+ for (const entry of Object.entries(fieldConfigs)) {
66
+ const [path, config] = entry;
67
+ const validator = getValidator(path, config?.validate);
68
+ if (validator) {
69
+ const unsubscribe = storeApi.subscribe(path, (value) => {
70
+ const error = validator(value, store);
71
+ if (!error) {
72
+ errorStore.reset(path);
73
+ }
74
+ else {
75
+ errorStore.set(path, error);
76
+ }
77
+ });
78
+ unsubscribeFns.push(unsubscribe);
79
+ }
76
80
  }
77
- }
81
+ return unsubscribeFns;
82
+ }, [fieldConfigs, storeApi, errorStore, store]);
83
+ useEffect(() => {
84
+ return () => {
85
+ for (const unsubscribe of unsubscribeFns) {
86
+ unsubscribe();
87
+ }
88
+ };
89
+ }, [unsubscribeFns]);
78
90
  return store;
79
91
  }
80
92
  /**
package/dist/impl.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { FieldPath, FieldPathValue, FieldValues } from './path';
2
- export { getNestedValue, getSnapshot, getStableKeys, isClass, isEqual, joinPath, notifyListeners, produce, rename, setExternalKeyOrder, setLeaf, subscribe, useDebounce, useObject, useSubscribe };
2
+ export { getNestedValue, getSnapshot, getStableKeys, isClass, isEqual, joinPath, notifyListeners, produce, rename, setExternalKeyOrder, setLeaf, subscribe, useDebounce, useObject };
3
3
  declare function setExternalKeyOrder(target: object, keys: string[]): void;
4
4
  declare function getStableKeys(value: unknown): string[];
5
5
  declare function isClass(value: unknown): boolean;
@@ -97,17 +97,6 @@ declare function useObject<T extends FieldValues, P extends FieldPath<T>>(key: s
97
97
  * @returns The debounced value at the path
98
98
  */
99
99
  declare function useDebounce<T extends FieldValues, P extends FieldPath<T>>(key: string, path: P, delay: number): FieldPathValue<T, P> | undefined;
100
- /**
101
- * React hook for side effects when a value changes.
102
- *
103
- * Unlike `use()`, this doesn't cause re-renders. Instead, it calls the
104
- * provided callback whenever the value changes, useful for syncing with
105
- * external systems or triggering effects.
106
- *
107
- * @param key - The full key path to subscribe to
108
- * @param onChange - Callback invoked with the new value on each change
109
- */
110
- declare function useSubscribe<T>(key: string, onChange: (value: T) => void): void;
111
100
  /**
112
101
  * Sets a value at a specific path within a namespace.
113
102
  *
package/dist/impl.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
2
2
  import rfcIsEqual from 'react-fast-compare';
3
3
  import { localStorageDelete, localStorageGet, localStorageSet } from './local_storage';
4
- export { getNestedValue, getSnapshot, getStableKeys, isClass, isEqual, joinPath, notifyListeners, produce, rename, setExternalKeyOrder, setLeaf, subscribe, useDebounce, useObject, useSubscribe };
4
+ export { getNestedValue, getSnapshot, getStableKeys, isClass, isEqual, joinPath, notifyListeners, produce, rename, setExternalKeyOrder, setLeaf, subscribe, useDebounce, useObject };
5
5
  const memoryStore = new Map();
6
6
  const listeners = new Map();
7
7
  const descendantListenerKeysByPrefix = new Map();
@@ -659,29 +659,6 @@ function useDebounce(key, path, delay) {
659
659
  }, [currentValue, delay, debouncedValue]);
660
660
  return debouncedValue;
661
661
  }
662
- /**
663
- * React hook for side effects when a value changes.
664
- *
665
- * Unlike `use()`, this doesn't cause re-renders. Instead, it calls the
666
- * provided callback whenever the value changes, useful for syncing with
667
- * external systems or triggering effects.
668
- *
669
- * @param key - The full key path to subscribe to
670
- * @param onChange - Callback invoked with the new value on each change
671
- */
672
- function useSubscribe(key, onChange) {
673
- const onChangeRef = useRef(onChange);
674
- useEffect(() => {
675
- onChangeRef.current = onChange;
676
- }, [onChange]);
677
- useEffect(() => {
678
- const unsubscribe = subscribe(key, () => {
679
- const value = getSnapshot(key);
680
- onChangeRef.current(value);
681
- });
682
- return unsubscribe;
683
- }, [key]);
684
- }
685
662
  /**
686
663
  * Sets a value at a specific path within a namespace.
687
664
  *
package/dist/root.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useRef, useSyncExternalStore } from 'react';
2
- import { getNestedValue, getSnapshot, isEqual, joinPath, notifyListeners, produce, rename, setLeaf, subscribe, useDebounce, useObject, useSubscribe } from './impl';
2
+ import { getNestedValue, getSnapshot, isEqual, joinPath, notifyListeners, produce, rename, setLeaf, subscribe, useDebounce, useObject } from './impl';
3
3
  import { createRootNode } from './node';
4
4
  export { createStoreRoot };
5
5
  /**
@@ -34,9 +34,9 @@ function createStoreRoot(namespace, defaultValue, options = {}) {
34
34
  value: (path) => getSnapshot(joinPath(namespace, path)),
35
35
  reset: (path) => produce(joinPath(namespace, path), undefined, false, memoryOnly),
36
36
  rename: (path, oldKey, newKey) => rename(joinPath(namespace, path), oldKey, newKey),
37
- subscribe: (path, listener) =>
38
- // eslint-disable-next-line react-hooks/rules-of-hooks
39
- useSubscribe(joinPath(namespace, path), listener),
37
+ subscribe: (path, listener) => () => {
38
+ subscribe(joinPath(namespace, path), () => listener(getSnapshot(joinPath(namespace, path))));
39
+ },
40
40
  useCompute: (path, fn, deps) => {
41
41
  const fullPath = joinPath(namespace, path);
42
42
  const fnRef = useRef(fn);
package/dist/types.d.ts CHANGED
@@ -59,8 +59,11 @@ type StoreRoot<T extends FieldValues> = {
59
59
  reset: <P extends FieldPath<T>>(path: P) => void;
60
60
  /** Rename a key in an object. */
61
61
  rename: <P extends FieldPath<T>>(path: P, oldKey: string, newKey: string) => void;
62
- /** Subscribe to changes at path and invoke listener with the new value. */
63
- subscribe: <P extends FieldPath<T>>(path: P, listener: (value: FieldPathValue<T, P>) => void) => void;
62
+ /** Subscribe to changes at path and invoke listener with the new value
63
+ *
64
+ * @returns A function to unsubscribe from the path.
65
+ */
66
+ subscribe: <P extends FieldPath<T>>(path: P, listener: (value: FieldPathValue<T, P>) => void) => () => void;
64
67
  /** Compute a derived value from the current value, similar to useState + useMemo */
65
68
  useCompute: <P extends FieldPath<T>, R>(path: P, fn: (value: FieldPathValue<T, P>) => R, deps?: readonly unknown[]) => R;
66
69
  /** Notify listeners at path. */
@@ -86,8 +89,19 @@ type ValueState<T> = {
86
89
  set(value: T | undefined | ((prev: T) => T), skipUpdate?: boolean): void;
87
90
  /** Delete value at path (for arrays, removes index; for objects, deletes key). */
88
91
  reset(): void;
89
- /** Subscribe to changes at path and invoke listener with the new value. */
90
- subscribe(listener: (value: T) => void): void;
92
+ /** Subscribe to changes at path and invoke listener with the new value.
93
+ *
94
+ * @returns A function to unsubscribe.
95
+ * @example
96
+ *
97
+ * useEffect(() => {
98
+ * const unsubscribe = store.a.b.c.subscribe(value => {
99
+ * console.log(value)
100
+ * })
101
+ * return unsubscribe
102
+ * }, [])
103
+ */
104
+ subscribe(listener: (value: T) => void): () => void;
91
105
  /** Compute a derived value from the current value, similar to useState + useMemo */
92
106
  useCompute: <R>(fn: (value: T) => R, deps?: readonly unknown[]) => R;
93
107
  /** Ensure the value is an array. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juststore",
3
- "version": "0.3.5",
3
+ "version": "0.4.1",
4
4
  "description": "A small, expressive, and type-safe state management library for React.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",