juststore 0.3.2 → 0.3.3

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.
Files changed (2) hide show
  1. package/dist/root.js +24 -16
  2. package/package.json +9 -9
package/dist/root.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useRef, useSyncExternalStore } from 'react';
2
- import { getNestedValue, getSnapshot, joinPath, notifyListeners, produce, rename, setLeaf, subscribe, useDebounce, useObject, useSubscribe } from './impl';
2
+ import { getNestedValue, getSnapshot, isEqual, joinPath, notifyListeners, produce, rename, setLeaf, subscribe, useDebounce, useObject, useSubscribe } from './impl';
3
3
  import { createRootNode } from './node';
4
4
  export { createStoreRoot };
5
5
  /**
@@ -41,19 +41,27 @@ function createStoreRoot(namespace, defaultValue, options = {}) {
41
41
  const fullPath = joinPath(namespace, path);
42
42
  const fnRef = useRef(fn);
43
43
  fnRef.current = fn;
44
- // Cache to avoid infinite loops - only recompute when store value changes
45
44
  const cacheRef = useRef(null);
46
45
  const subscribeToPath = useCallback((onStoreChange) => subscribe(fullPath, onStoreChange), [fullPath]);
47
46
  const getComputedSnapshot = useCallback(() => {
47
+ if (cacheRef.current && cacheRef.current.path !== fullPath) {
48
+ cacheRef.current = null;
49
+ }
48
50
  const storeValue = getSnapshot(fullPath);
49
- // Return cached result if store value hasn't changed
50
- if (cacheRef.current && cacheRef.current.storeValue === storeValue) {
51
+ if (cacheRef.current && isEqual(cacheRef.current.storeValue, storeValue)) {
52
+ // same store value, return the same computed value
53
+ return cacheRef.current.computed;
54
+ }
55
+ const computedNext = fnRef.current(storeValue);
56
+ // Important: even if storeValue changed, we should avoid forcing a re-render
57
+ // when the computed result is logically unchanged. `useSyncExternalStore`
58
+ // uses `Object.is` on the snapshot; returning the same reference will bail out.
59
+ if (cacheRef.current && isEqual(cacheRef.current.computed, computedNext)) {
60
+ cacheRef.current.storeValue = storeValue;
51
61
  return cacheRef.current.computed;
52
62
  }
53
- // Recompute and cache
54
- const computed = fnRef.current(storeValue);
55
- cacheRef.current = { storeValue, computed };
56
- return computed;
63
+ cacheRef.current = { path: fullPath, storeValue, computed: computedNext };
64
+ return computedNext;
57
65
  }, [fullPath]);
58
66
  return useSyncExternalStore(subscribeToPath, getComputedSnapshot, getComputedSnapshot);
59
67
  },
@@ -66,28 +74,28 @@ function createStoreRoot(namespace, defaultValue, options = {}) {
66
74
  });
67
75
  },
68
76
  useState: (path) => {
69
- const fullPathRef = useRef(joinPath(namespace, path));
77
+ const fullPath = joinPath(namespace, path);
70
78
  const setValue = useCallback((value) => {
71
79
  if (typeof value === 'function') {
72
- const currentValue = getSnapshot(fullPathRef.current);
80
+ const currentValue = getSnapshot(fullPath);
73
81
  const newValue = value(currentValue);
74
82
  return setLeaf(namespace, path, newValue, false, memoryOnly);
75
83
  }
76
84
  return setLeaf(namespace, path, value, false, memoryOnly);
77
- }, [path]);
78
- return [useObject(fullPathRef.current), setValue];
85
+ }, [fullPath, path]);
86
+ return [useObject(namespace, path), setValue];
79
87
  },
80
88
  Render: ({ path, children }) => {
81
- const fullPathRef = useRef(joinPath(namespace, path));
82
- const value = useObject(fullPathRef.current);
89
+ const fullPath = joinPath(namespace, path);
90
+ const value = useObject(namespace, path);
83
91
  const update = useCallback((value) => {
84
92
  if (typeof value === 'function') {
85
- const currentValue = getSnapshot(fullPathRef.current);
93
+ const currentValue = getSnapshot(fullPath);
86
94
  const newValue = value(currentValue);
87
95
  return setLeaf(namespace, path, newValue, false, memoryOnly);
88
96
  }
89
97
  return setLeaf(namespace, path, value, false, memoryOnly);
90
- }, [path]);
98
+ }, [fullPath, path]);
91
99
  return children(value, update);
92
100
  },
93
101
  Show: ({ path, children, on }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juststore",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "A small, expressive, and type-safe state management library for React.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -52,19 +52,19 @@
52
52
  "react-fast-compare": "^3.2.2"
53
53
  },
54
54
  "devDependencies": {
55
- "@eslint/js": "^9.39.1",
56
- "@types/node": "^24.10.2",
57
- "@types/react": "^19.2.1",
58
- "@typescript-eslint/eslint-plugin": "^8.49.0",
59
- "@typescript-eslint/parser": "^8.49.0",
60
- "eslint": "^9.39.1",
55
+ "@eslint/js": "^9.39.2",
56
+ "@types/node": "^24.10.4",
57
+ "@types/react": "^19.2.7",
58
+ "@typescript-eslint/eslint-plugin": "^8.50.1",
59
+ "@typescript-eslint/parser": "^8.50.1",
60
+ "eslint": "^9.39.2",
61
61
  "eslint-plugin-prettier": "^5.5.4",
62
62
  "eslint-plugin-react": "^7.37.5",
63
63
  "eslint-plugin-react-hooks": "^7.0.1",
64
- "eslint-plugin-react-refresh": "^0.4.24",
64
+ "eslint-plugin-react-refresh": "^0.4.26",
65
65
  "husky": "^9.1.7",
66
66
  "prettier": "^3.7.4",
67
- "react": "^19.2.1",
67
+ "react": "^19.2.3",
68
68
  "typescript": "^5.9.3"
69
69
  }
70
70
  }