dev-react-microstore 1.1.2 → 2.0.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/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var h=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var z=Object.getOwnPropertyNames,P=Object.getOwnPropertySymbols;var I=Object.prototype.hasOwnProperty,V=Object.prototype.propertyIsEnumerable;var v=(r,e,t)=>e in r?h(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t,j=(r,e)=>{for(var t in e||(e={}))I.call(e,t)&&v(r,t,e[t]);if(P)for(var t of P(e))V.call(e,t)&&v(r,t,e[t]);return r};var C=(r,e)=>{for(var t in e)h(r,t,{get:e[t],enumerable:!0})},F=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of z(e))!I.call(r,o)&&o!==t&&h(r,o,{get:()=>e[o],enumerable:!(s=R(e,o))||s.enumerable});return r};var O=r=>F(h({},"__esModule",{value:!0}),r);var A={};C(A,{createStoreState:()=>w,useStoreSelector:()=>N});module.exports=O(A);var y=require("react");function w(r){let e=r,t=new Map;return{get:()=>e,set:n=>{var i;if(n==null||typeof n!="object"||Array.isArray(n)){console.error(`set() called with invalid value: ${n===null?"null":n===void 0?"undefined":typeof n}`);return}let u=!1,c=[];for(let S in n)if(Object.prototype.hasOwnProperty.call(n,S)){let d=S;d in e&&!Object.is(e[d],n[d])&&(u=!0,c.push(d))}if(u){e=j(j({},e),n);for(let S of c)(i=t.get(S))==null||i.forEach(d=>d())}},subscribe:(n,u)=>{for(let c of n)t.has(c)||t.set(c,new Set),t.get(c).add(u);return()=>{var c;for(let i of n)(c=t.get(i))==null||c.delete(u)}},select:n=>{let u={},c=e;for(let i of n)u[i]=c[i];return u}}}function E(r,e){return r.length===e.length&&r.every((t,s)=>t===e[s])}function L(r,e,t){let s={};for(let{key:o,compare:p}of t){let T=e[o],n=r[o],u=T===void 0?!0:p?p(T,n):!Object.is(T,n);s[o]=u?n:T}return s}function N(r,e){let t=(0,y.useRef)(r.get()),s=(0,y.useRef)({}),o=(0,y.useRef)(null),p=(0,y.useRef)(null),T=(0,y.useRef)(null),n=(0,y.useRef)(!0);if(!o.current||!E(o.current,e)){let l=[],f=[];for(let a of e)if(typeof a=="string"){let b=a;l.push({key:b}),f.push(b)}else for(let[b,m]of Object.entries(a)){let k=b;l.push({key:k,compare:m}),f.push(k)}p.current=l,T.current=f,o.current=e}let u=p.current,c=T.current,i=()=>{let l=r.get(),f=t.current,a=n.current;return(a||u.some(({key:m,compare:k})=>{var x;let K=f[m],g=l[m];return(x=k==null?void 0:k(K,g))!=null?x:!Object.is(K,g)}))&&(a&&(n.current=!1),t.current=l,s.current=L(l,s.current,u)),s.current},S=(()=>{let l=r.get();return c.reduce((f,a)=>(f[a]=l[a],f),{})})();return(0,y.useSyncExternalStore)(l=>r.subscribe(c,l),i,()=>S)}0&&(module.exports={createStoreState,useStoreSelector});
1
+ "use strict";var K=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var I=(o,e)=>{for(var t in e)K(o,t,{get:e[t],enumerable:!0})},R=(o,e,t,c)=>{if(e&&typeof e=="object"||typeof e=="function")for(let d of v(e))!j.call(o,d)&&d!==t&&K(o,d,{get:()=>e[d],enumerable:!(c=P(e,d))||c.enumerable});return o};var C=o=>R(K({},"__esModule",{value:!0}),o);var w={};I(w,{createStoreState:()=>F,useStoreSelector:()=>E});module.exports=C(w);var T=require("react");function V(o,e){let t;return function(...c){t!==void 0&&clearTimeout(t),t=setTimeout(()=>o.apply(this,c),e)}}function F(o){let e=o,t=new Map,c=new Map;return{get:()=>e,set:l=>{if(!l)return;let a=[];for(let n in l){let u=n,m=e[u],r=l[u];m!==r&&(Object.is(m,r)||(e[u]=r,a.push(u)))}if(a.length!==0)for(let n of a)c.has(n)||c.set(n,V(()=>{var u;(u=t.get(n))==null||u.forEach(m=>m())},5)),c.get(n)()},subscribe:(l,a)=>{for(let n of l)t.has(n)||t.set(n,new Set),t.get(n).add(a);return()=>{var n;for(let u of l)(n=t.get(u))==null||n.delete(a)}},select:l=>{let a={},n=e;for(let u of l)a[u]=n[u];return a}}}function z(o,e){return o.length===e.length&&o.every((t,c)=>t===e[c])}function E(o,e){let t=(0,T.useRef)({}),c=(0,T.useRef)(null),d=(0,T.useRef)(null),x=(0,T.useRef)(null),h=(0,T.useRef)(!0),b=(0,T.useRef)({}),l=(0,T.useRef)(null);if(!c.current||!z(c.current,e)){let r=[],p=[];for(let k of e)if(typeof k=="string"){let y=k;r.push({key:y}),p.push(y)}else{let y=k;for(let s in y){let f=y[s],i=s;r.push({key:i,compare:f}),p.push(i)}}d.current=r,x.current=p,c.current=e}let a=d.current,n=x.current,u=()=>{let r=o.get();if(h.current){h.current=!1;let s={};for(let{key:f}of a){let i=r[f];b.current[f]=i,s[f]=i}return t.current=s,s}if(!(()=>{var s;for(let{key:f,compare:i}of a){let S=b.current[f],g=r[f];if(S===void 0||((s=i==null?void 0:i(S,g))!=null?s:!Object.is(S,g)))return!0}return!1})())return t.current;let y={};for(let{key:s,compare:f}of a){let i=b.current[s],S=r[s];i===void 0||(f?!f(i,S):!Object.is(i,S))?(b.current[s]=S,y[s]=S):y[s]=i}return t.current=y,y},m=(0,T.useMemo)(()=>{let r=o.get(),p={};for(let k of n)p[k]=r[k];return p},[n]);if(!l.current||x.current!==n){let r=n;l.current=p=>o.subscribe(r,p)}return(0,T.useSyncExternalStore)(l.current,u,()=>m)}0&&(module.exports={createStoreState,useStoreSelector});
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var v=Object.defineProperty;var x=Object.getOwnPropertySymbols;var I=Object.prototype.hasOwnProperty,R=Object.prototype.propertyIsEnumerable;var P=(n,e,t)=>e in n?v(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,h=(n,e)=>{for(var t in e||(e={}))I.call(e,t)&&P(n,t,e[t]);if(x)for(var t of x(e))R.call(e,t)&&P(n,t,e[t]);return n};import{useRef as k,useSyncExternalStore as z}from"react";function w(n){let e=n,t=new Map;return{get:()=>e,set:r=>{var l;if(r==null||typeof r!="object"||Array.isArray(r)){console.error(`set() called with invalid value: ${r===null?"null":r===void 0?"undefined":typeof r}`);return}let c=!1,o=[];for(let p in r)if(Object.prototype.hasOwnProperty.call(r,p)){let f=p;f in e&&!Object.is(e[f],r[f])&&(c=!0,o.push(f))}if(c){e=h(h({},e),r);for(let p of o)(l=t.get(p))==null||l.forEach(f=>f())}},subscribe:(r,c)=>{for(let o of r)t.has(o)||t.set(o,new Set),t.get(o).add(c);return()=>{var o;for(let l of r)(o=t.get(l))==null||o.delete(c)}},select:r=>{let c={},o=e;for(let l of r)c[l]=o[l];return c}}}function V(n,e){return n.length===e.length&&n.every((t,u)=>t===e[u])}function C(n,e,t){let u={};for(let{key:a,compare:d}of t){let y=e[a],r=n[a],c=y===void 0?!0:d?d(y,r):!Object.is(y,r);u[a]=c?r:y}return u}function E(n,e){let t=k(n.get()),u=k({}),a=k(null),d=k(null),y=k(null),r=k(!0);if(!a.current||!V(a.current,e)){let s=[],T=[];for(let i of e)if(typeof i=="string"){let b=i;s.push({key:b}),T.push(b)}else for(let[b,m]of Object.entries(i)){let S=b;s.push({key:S,compare:m}),T.push(S)}d.current=s,y.current=T,a.current=e}let c=d.current,o=y.current,l=()=>{let s=n.get(),T=t.current,i=r.current;return(i||c.some(({key:m,compare:S})=>{var g;let j=T[m],K=s[m];return(g=S==null?void 0:S(j,K))!=null?g:!Object.is(j,K)}))&&(i&&(r.current=!1),t.current=s,u.current=C(s,u.current,c)),u.current},p=(()=>{let s=n.get();return o.reduce((T,i)=>(T[i]=s[i],T),{})})();return z(s=>n.subscribe(o,s),l,()=>p)}export{w as createStoreState,E as useStoreSelector};
1
+ import{useMemo as K,useRef as S,useSyncExternalStore as P}from"react";function v(T,c){let t;return function(...a){t!==void 0&&clearTimeout(t),t=setTimeout(()=>T.apply(this,a),c)}}function C(T){let c=T,t=new Map,a=new Map;return{get:()=>c,set:i=>{if(!i)return;let u=[];for(let e in i){let r=e,k=c[r],n=i[r];k!==n&&(Object.is(k,n)||(c[r]=n,u.push(r)))}if(u.length!==0)for(let e of u)a.has(e)||a.set(e,v(()=>{var r;(r=t.get(e))==null||r.forEach(k=>k())},5)),a.get(e)()},subscribe:(i,u)=>{for(let e of i)t.has(e)||t.set(e,new Set),t.get(e).add(u);return()=>{var e;for(let r of i)(e=t.get(r))==null||e.delete(u)}},select:i=>{let u={},e=c;for(let r of i)u[r]=e[r];return u}}}function j(T,c){return T.length===c.length&&T.every((t,a)=>t===c[a])}function V(T,c){let t=S({}),a=S(null),x=S(null),b=S(null),h=S(!0),m=S({}),i=S(null);if(!a.current||!j(a.current,c)){let n=[],y=[];for(let p of c)if(typeof p=="string"){let f=p;n.push({key:f}),y.push(f)}else{let f=p;for(let o in f){let l=f[o],s=o;n.push({key:s,compare:l}),y.push(s)}}x.current=n,b.current=y,a.current=c}let u=x.current,e=b.current,r=()=>{let n=T.get();if(h.current){h.current=!1;let o={};for(let{key:l}of u){let s=n[l];m.current[l]=s,o[l]=s}return t.current=o,o}if(!(()=>{var o;for(let{key:l,compare:s}of u){let d=m.current[l],g=n[l];if(d===void 0||((o=s==null?void 0:s(d,g))!=null?o:!Object.is(d,g)))return!0}return!1})())return t.current;let f={};for(let{key:o,compare:l}of u){let s=m.current[o],d=n[o];s===void 0||(l?!l(s,d):!Object.is(s,d))?(m.current[o]=d,f[o]=d):f[o]=s}return t.current=f,f},k=K(()=>{let n=T.get(),y={};for(let p of e)y[p]=n[p];return y},[e]);if(!i.current||b.current!==e){let n=e;i.current=y=>T.subscribe(n,y)}return P(i.current,r,()=>k)}export{C as createStoreState,V as useStoreSelector};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dev-react-microstore",
3
- "version": "1.1.2",
3
+ "version": "2.0.0",
4
4
  "description": "A minimal global state manager for React with fine-grained subscriptions.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -1,69 +1,79 @@
1
- import { useRef, useSyncExternalStore } from 'react';
2
- type StoreListener = () => void;
1
+ import { useMemo, useRef, useSyncExternalStore } from 'react'
3
2
 
3
+ type StoreListener = () => void;
4
4
 
5
+ function debounce<T extends (...args: any[]) => void>(fn: T, delay: number): T {
6
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
7
+ return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
8
+ if (timeoutId !== undefined) {
9
+ clearTimeout(timeoutId);
10
+ }
11
+ timeoutId = setTimeout(() => fn.apply(this, args), delay);
12
+ } as T;
13
+ }
5
14
 
6
15
  export function createStoreState<T extends object>(initialState: T) {
7
- let state = initialState;
16
+ let state = initialState;
8
17
 
9
- // Map from key to set of subscribers interested in that key
10
- const keyListeners = new Map<keyof T, Set<StoreListener>>();
18
+ const keyListeners = new Map<keyof T, Set<StoreListener>>();
19
+ const debouncedNotifiers = new Map<keyof T, () => void>();
11
20
 
12
- const get = () => state;
21
+ const get = () => state;
13
22
 
14
- const set = (next: Partial<T>) => {
15
- if (next === null || next === undefined || typeof next !== 'object' || Array.isArray(next)) {
16
- console.error(`set() called with invalid value: ${next === null ? 'null' : next === undefined ? 'undefined' : typeof next}`);
17
- return;
18
- }
19
-
20
- let changed = false;
21
- const updatedKeys: (keyof T)[] = [];
22
-
23
- for (const key in next) {
24
- if (Object.prototype.hasOwnProperty.call(next, key)) {
25
- const typedKey = key as keyof T;
26
- if (typedKey in state && !Object.is(state[typedKey], next[typedKey])) {
27
- changed = true;
28
- updatedKeys.push(typedKey);
23
+ const set = (next: Partial<T>) => {
24
+ if (!next) return;
25
+
26
+ const updatedKeys: (keyof T)[] = [];
27
+ for (const key in next) {
28
+ const typedKey = key as keyof T;
29
+ const currentValue = state[typedKey];
30
+ const nextValue = next[typedKey];
31
+
32
+ if (currentValue === nextValue) continue;
33
+
34
+ if (!Object.is(currentValue, nextValue)) {
35
+ state[typedKey] = nextValue!;
36
+ updatedKeys.push(typedKey);
37
+ }
29
38
  }
30
- }
31
- }
32
39
 
33
- if (!changed) return;
40
+ if (updatedKeys.length === 0) return;
34
41
 
35
- state = { ...state, ...next };
42
+ for (const key of updatedKeys) {
43
+ if (!debouncedNotifiers.has(key)) {
44
+ debouncedNotifiers.set(key, debounce(() => {
45
+ keyListeners.get(key)?.forEach(listener => listener());
46
+ }, 5));
47
+ }
48
+ debouncedNotifiers.get(key)!();
49
+ }
50
+ };
36
51
 
37
- for (const key of updatedKeys) {
38
- keyListeners.get(key)?.forEach(listener => listener());
39
- }
40
- };
41
-
42
- const subscribe = (keys: (keyof T)[], listener: StoreListener): (() => void) => {
43
- for (const key of keys) {
44
- if (!keyListeners.has(key)) {
45
- keyListeners.set(key, new Set());
46
- }
47
- keyListeners.get(key)!.add(listener);
48
- }
52
+ const subscribe = (keys: (keyof T)[], listener: StoreListener): (() => void) => {
53
+ for (const key of keys) {
54
+ if (!keyListeners.has(key)) {
55
+ keyListeners.set(key, new Set());
56
+ }
57
+ keyListeners.get(key)!.add(listener);
58
+ }
49
59
 
50
- return () => {
51
- for (const key of keys) {
52
- keyListeners.get(key)?.delete(listener);
53
- }
60
+ return () => {
61
+ for (const key of keys) {
62
+ keyListeners.get(key)?.delete(listener);
63
+ }
64
+ };
54
65
  };
55
- };
56
66
 
57
- const select = <K extends keyof T>(keys: K[]): Pick<T, K> => {
58
- const result = {} as Pick<T, K>;
59
- const currentState = state;
60
- for (const key of keys) {
61
- result[key] = currentState[key];
62
- }
63
- return result;
64
- };
67
+ const select = <K extends keyof T>(keys: K[]): Pick<T, K> => {
68
+ const result = {} as Pick<T, K>;
69
+ const currentState = state;
70
+ for (const key of keys) {
71
+ result[key] = currentState[key];
72
+ }
73
+ return result;
74
+ };
65
75
 
66
- return { get, set, subscribe, select };
76
+ return { get, set, subscribe, select };
67
77
  }
68
78
 
69
79
  type StoreType<T extends object> = ReturnType<typeof createStoreState<T>>;
@@ -75,115 +85,136 @@ type CustomSelector<T extends object> = { [K in keyof T]?: CompareFn<T[K]> };
75
85
  type SelectorInput<T extends object> = ReadonlyArray<KeySelector<T> | CustomSelector<T>>;
76
86
 
77
87
  type ExtractSelectorKeys<T extends object, S extends SelectorInput<T>> = {
78
- [K in S[number] extends infer Item
88
+ [K in S[number] extends infer Item
79
89
  ? Item extends keyof T
80
- ? Item
81
- : keyof Item
90
+ ? Item
91
+ : keyof Item
82
92
  : never]: T[K];
83
93
  };
84
94
 
85
95
  type Picked<T extends object, S extends SelectorInput<T>> = ExtractSelectorKeys<T, S>;
86
96
 
87
97
  type NormalizedSelector<T extends object> = {
88
- key: keyof T;
89
- compare?: CompareFn<T[keyof T]>;
98
+ key: keyof T;
99
+ compare?: CompareFn<T[keyof T]>;
90
100
  };
91
101
 
92
- // Helper functions for useStoreSelector
93
102
  function shallowEqualSelector<T extends object>(
94
- a: SelectorInput<T>,
95
- b: SelectorInput<T>
103
+ a: SelectorInput<T>,
104
+ b: SelectorInput<T>
96
105
  ): boolean {
97
- return a.length === b.length && a.every((item, i) => item === b[i]);
98
- }
99
-
100
- function pickKeys<T extends object>(
101
- base: T,
102
- prev: Partial<T>,
103
- selectors: NormalizedSelector<T>[]
104
- ): Partial<T> {
105
- const out: Partial<T> = {};
106
- for (const { key, compare } of selectors) {
107
- const prevVal = prev[key];
108
- const nextVal = base[key];
109
- const changed = prevVal === undefined ? true : (compare ? compare(prevVal, nextVal) : !Object.is(prevVal, nextVal));
110
- out[key] = changed ? nextVal : prevVal;
111
- }
112
- return out;
106
+ return a.length === b.length && a.every((item, i) => item === b[i]);
113
107
  }
114
108
 
115
109
  export function useStoreSelector<T extends object, S extends SelectorInput<T>>(
116
- store: StoreType<T>,
117
- selector: S
110
+ store: StoreType<T>,
111
+ selector: S
118
112
  ): Picked<T, S> {
119
- const lastState = useRef(store.get());
120
- const lastSelected = useRef<Partial<T>>({});
121
- const prevSelector = useRef<SelectorInput<T> | null>(null);
122
- const normalizedRef = useRef<NormalizedSelector<T>[] | null>(null);
123
- const keysRef = useRef<(keyof T)[] | null>(null);
124
- const isFirstRunRef = useRef(true);
125
-
126
- if (!prevSelector.current || !shallowEqualSelector(prevSelector.current, selector)) {
127
- const normalized: NormalizedSelector<T>[] = [];
128
- const keys: (keyof T)[] = [];
129
-
130
- for (const item of selector) {
131
- if (typeof item === 'string') {
132
- const key = item as keyof T;
133
- normalized.push({ key });
134
- keys.push(key);
135
- } else {
136
- for (const [key, compare] of Object.entries(item)) {
137
- const typedKey = key as keyof T;
138
- normalized.push({ key: typedKey, compare: compare as CompareFn<T[keyof T]> });
139
- keys.push(typedKey);
113
+ const lastSelected = useRef<Partial<T>>({});
114
+ const prevSelector = useRef<SelectorInput<T> | null>(null);
115
+ const normalizedRef = useRef<NormalizedSelector<T>[] | null>(null);
116
+ const keysRef = useRef<(keyof T)[] | null>(null);
117
+ const isFirstRunRef = useRef(true);
118
+ const lastValues = useRef<Partial<T>>({});
119
+ const subscribeRef = useRef<((onStoreChange: () => void) => () => void) | null>(null);
120
+
121
+ if (!prevSelector.current || !shallowEqualSelector(prevSelector.current, selector)) {
122
+ const normalized: NormalizedSelector<T>[] = [];
123
+ const keys: (keyof T)[] = [];
124
+
125
+ for (const item of selector) {
126
+ if (typeof item === 'string') {
127
+ const key = item as keyof T;
128
+ normalized.push({ key });
129
+ keys.push(key);
130
+ } else {
131
+ const customSelector = item as CustomSelector<T>;
132
+ for (const key in customSelector) {
133
+ const compare = customSelector[key as keyof typeof customSelector];
134
+ const typedKey = key as keyof T;
135
+ normalized.push({ key: typedKey, compare: compare as CompareFn<T[keyof T]> });
136
+ keys.push(typedKey);
137
+ }
138
+ }
140
139
  }
141
- }
142
- }
143
-
144
- normalizedRef.current = normalized;
145
- keysRef.current = keys;
146
- prevSelector.current = selector;
147
- }
148
140
 
149
- const normalized = normalizedRef.current!;
150
- const keys = keysRef.current!;
141
+ normalizedRef.current = normalized;
142
+ keysRef.current = keys;
143
+ prevSelector.current = selector;
144
+ }
151
145
 
152
- const getSnapshot = () => {
153
- const current = store.get();
154
- const prev = lastState.current;
146
+ const normalized = normalizedRef.current!;
147
+ const keys = keysRef.current!;
148
+
149
+ const getSnapshot = () => {
150
+ const current = store.get();
151
+ const isFirstRun = isFirstRunRef.current;
152
+
153
+ if (isFirstRun) {
154
+ isFirstRunRef.current = false;
155
+ const result = {} as Partial<T>;
156
+ for (const { key } of normalized) {
157
+ const value = current[key];
158
+ lastValues.current[key] = value;
159
+ result[key] = value;
160
+ }
161
+ lastSelected.current = result;
162
+ return result as Picked<T, S>;
163
+ }
155
164
 
156
- const isFirstRun = isFirstRunRef.current;
165
+ // Check for changes with early return
166
+ const hasChanges = () => {
167
+ for (const { key, compare } of normalized) {
168
+ const prevVal = lastValues.current[key];
169
+ const nextVal = current[key];
170
+ if (prevVal === undefined ? true : (compare?.(prevVal, nextVal) ?? !Object.is(prevVal, nextVal))) {
171
+ return true;
172
+ }
173
+ }
174
+ return false;
175
+ };
176
+
177
+ if (!hasChanges()) {
178
+ return lastSelected.current as Picked<T, S>;
179
+ }
157
180
 
158
- const changed = isFirstRun || normalized.some(({ key, compare }) => {
159
- const prevVal = prev[key];
160
- const nextVal = current[key];
161
- return compare?.(prevVal, nextVal) ?? !Object.is(prevVal, nextVal);
162
- });
181
+ // Build result only when changes detected
182
+ const result = {} as Partial<T>;
183
+ for (const { key, compare } of normalized) {
184
+ const prevVal = lastValues.current[key];
185
+ const nextVal = current[key];
186
+
187
+ // Hoist the comparison logic
188
+ const isFirstTime = prevVal === undefined;
189
+ const changed = isFirstTime || (compare ? !compare(prevVal, nextVal) : !Object.is(prevVal, nextVal));
190
+
191
+ if (changed) {
192
+ lastValues.current[key] = nextVal;
193
+ result[key] = nextVal;
194
+ } else {
195
+ result[key] = prevVal;
196
+ }
197
+ }
163
198
 
164
- if (!changed) {
165
- return lastSelected.current as Picked<T, S>;
166
- }
167
-
168
- if (isFirstRun) {
169
- isFirstRunRef.current = false;
170
- }
199
+ lastSelected.current = result;
200
+ return result as Picked<T, S>;
201
+ };
171
202
 
172
- lastState.current = current;
173
- lastSelected.current = pickKeys(current, lastSelected.current, normalized);
174
- return lastSelected.current as Picked<T, S>;
175
- };
203
+ const staticSnapshot = useMemo(() => {
204
+ const current = store.get();
205
+ const result = {} as Partial<T>;
206
+ for (const key of keys) {
207
+ result[key] = current[key];
208
+ }
209
+ return result as Picked<T, S>;
210
+ }, [keys]);
176
211
 
177
- const staticSnapshot = (() => {
178
- const current = store.get();
179
- return keys.reduce((acc, key) => {
180
- acc[key] = current[key];
181
- return acc;
182
- }, {} as Partial<T>) as Picked<T, S>;
183
- })();
184
212
 
185
- const subscribe = (onStoreChange: () => void) =>
186
- store.subscribe(keys, onStoreChange);
213
+ if (!subscribeRef.current || keysRef.current !== keys) {
214
+ const currentKeys = keys;
215
+ subscribeRef.current = (onStoreChange: () => void) =>
216
+ store.subscribe(currentKeys, onStoreChange);
217
+ }
187
218
 
188
- return useSyncExternalStore(subscribe, getSnapshot, () => staticSnapshot);
219
+ return useSyncExternalStore(subscribeRef.current, getSnapshot, () => staticSnapshot);
189
220
  }