yuustate 3.9.0 → 3.10.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.
@@ -350,7 +350,7 @@ export declare const createQuery: <TData, TVariable extends StoreKey = never, TE
350
350
  */
351
351
  initialDataIsStale?: boolean;
352
352
  } | {
353
- initialData?: never;
353
+ initialData?: never | undefined;
354
354
  initialDataIsStale?: never;
355
355
  })) => QueryState<TData, TError>) & {
356
356
  setState: (value: SetStateInput<QueryState<TData, TError>>) => void;
@@ -31,5 +31,5 @@ export declare const createStore: <TState extends Record<string, any>>(initialSt
31
31
  /**
32
32
  * Initial state used on first render (and will also update the store state right after that)
33
33
  */
34
- initialState?: Partial<TState>;
34
+ initialState?: Partial<TState> | undefined;
35
35
  }) => TState) & import("../vanilla.d.mts").StoreApi<TState>;
@@ -4,7 +4,6 @@ export declare const getValueByPath: (obj: any, path: Path) => any;
4
4
  export declare const isPrefixPath: (candidatePrefix: Path, targetPath: Path) => boolean;
5
5
  export declare const compressPaths: (paths: Path[]) => Path[];
6
6
  export declare const useStoreStateProxy: <TState extends Record<string, any>>(storeState: TState) => readonly [TState, import("react").RefObject<Path[]>];
7
- export declare const NO_INITIAL_VALUE: {};
8
7
  export declare const useStoreStateWithInitializer: <TState extends Record<string, any>>(store: StoreApi<TState>, initialState?: Partial<TState>) => readonly [TState, import("react").RefObject<WeakMap<StoreApi<TState>, number>>];
9
8
  /**
10
9
  * React hook for subscribing to a store using automatic dependency tracking.
@@ -29,6 +28,6 @@ export declare const useStoreStateWithInitializer: <TState extends Record<string
29
28
  * // Component will only re-render if `user.name` changes
30
29
  */
31
30
  export declare const useStoreState: <TState extends Record<string, any>>(store: StoreApi<TState>, options?: {
32
- initialState?: Partial<TState>;
31
+ initialState?: Partial<TState> | undefined;
33
32
  }) => TState;
34
33
  export {};
package/esm/react.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { useLayoutEffect, useEffect, useState, useRef, useMemo, useCallback } from 'react';
1
+ import { useLayoutEffect, useEffect, useRef, useSyncExternalStore, useCallback, useMemo, useState } from 'react';
2
2
  import { isClient, initStore, getHash, noop } from 'yuustate/vanilla';
3
3
 
4
4
  const useIsomorphicLayoutEffect = isClient ? useLayoutEffect : useEffect;
@@ -49,34 +49,46 @@ const useStoreStateProxy = (storeState) => {
49
49
  }, [storeState]);
50
50
  return [trackedState, usedPathsRef];
51
51
  };
52
- const NO_INITIAL_VALUE = {};
53
- const useStoreStateWithInitializer = (store, initialState = NO_INITIAL_VALUE) => {
52
+ const useStoreStateWithInitializer = (store, initialState) => {
54
53
  const initiatedAt = useRef(new WeakMap([[store, 0]]));
55
54
  useEffect(() => {
56
- if (initialState === NO_INITIAL_VALUE || initiatedAt.current.get(store)) return;
55
+ if (initialState === void 0 || initiatedAt.current.get(store)) return;
57
56
  store.setState(initialState);
58
57
  initiatedAt.current.set(store, Date.now());
59
58
  }, [store, initialState]);
60
59
  const storeState = store.getState();
61
- const finalState = initialState === NO_INITIAL_VALUE || initiatedAt.current.get(store) ? storeState : { ...storeState, ...initialState };
60
+ const finalState = initialState === void 0 || initiatedAt.current.get(store) ? storeState : { ...storeState, ...initialState };
62
61
  return [finalState, initiatedAt];
63
62
  };
64
63
  const useStoreState = (store, options = {}) => {
65
64
  const [state] = useStoreStateWithInitializer(store, options.initialState);
66
65
  const [trackedState, usedPathsRef] = useStoreStateProxy(state);
67
- const [, reRender] = useState({});
68
- useEffect(() => {
69
- return store.subscribe((nextState, prevState, changedKeys) => {
70
- const paths = compressPaths(usedPathsRef.current);
71
- for (const path of paths) {
72
- const rootKey = path[0];
73
- if (!changedKeys.includes(rootKey)) continue;
74
- const prevVal = getValueByPath(prevState, path);
75
- const nextVal = getValueByPath(nextState, path);
76
- if (!Object.is(prevVal, nextVal)) return reRender({});
77
- }
78
- });
79
- }, [store]);
66
+ const snapshotRef = useRef(store.getState());
67
+ const getSnapshot = () => snapshotRef.current;
68
+ useSyncExternalStore(
69
+ useCallback(
70
+ (onStoreChange) => {
71
+ return store.subscribe((nextState, prevState, changedKeys) => {
72
+ const paths = compressPaths(usedPathsRef.current);
73
+ for (const path of paths) {
74
+ const rootKey = path[0];
75
+ if (!changedKeys.includes(rootKey)) continue;
76
+ const prevVal = getValueByPath(prevState, path);
77
+ const nextVal = getValueByPath(nextState, path);
78
+ if (!Object.is(prevVal, nextVal)) {
79
+ snapshotRef.current = nextState;
80
+ return onStoreChange();
81
+ }
82
+ }
83
+ });
84
+ },
85
+ // False positive (missing dependency: 'usedPathsRef'): usedPathsRef is a ref, no need to add to deps
86
+ // eslint-disable-next-line react-hooks/exhaustive-deps
87
+ [store]
88
+ ),
89
+ getSnapshot,
90
+ getSnapshot
91
+ );
80
92
  return trackedState;
81
93
  };
82
94
 
@@ -413,14 +425,14 @@ const createQuery = (queryFn, options = {}) => {
413
425
  }
414
426
  const useStore = (options2 = {}) => {
415
427
  const {
416
- initialData = NO_INITIAL_VALUE,
428
+ initialData,
417
429
  initialDataIsStale = false,
418
430
  revalidateOnMount = true,
419
431
  keepPreviousData
420
432
  } = options2;
421
433
  const [storeState, initialDataInitiatedAt] = useStoreStateWithInitializer(
422
434
  store,
423
- initialData === NO_INITIAL_VALUE ? void 0 : {
435
+ initialData === void 0 ? void 0 : {
424
436
  state: "SUCCESS",
425
437
  isSuccess: true,
426
438
  data: initialData
@@ -444,22 +456,35 @@ const createQuery = (queryFn, options = {}) => {
444
456
  { ...storeStateToBeUsed, isPending: true }
445
457
  ) : storeStateToBeUsed
446
458
  );
447
- const [, reRender] = useState({});
448
- useEffect(() => {
449
- return store.subscribe((nextState, prevState2, changedKeys) => {
450
- if (prevState2.state === "INITIAL" && !prevState2.isPending && nextState.isPending && !nextState.isRetrying) {
451
- return;
452
- }
453
- const paths = compressPaths(usedPathsRef.current);
454
- for (const path of paths) {
455
- const rootKey = path[0];
456
- if (!changedKeys.includes(rootKey)) continue;
457
- const prevVal = getValueByPath(prevState2, path);
458
- const nextVal = getValueByPath(nextState, path);
459
- if (!Object.is(prevVal, nextVal)) return reRender({});
460
- }
461
- });
462
- }, [store]);
459
+ const snapshotRef = useRef(store.getState());
460
+ const getSnapshot = () => snapshotRef.current;
461
+ useSyncExternalStore(
462
+ useCallback(
463
+ (onStoreChange) => {
464
+ return store.subscribe((nextState, prevState2, changedKeys) => {
465
+ if (prevState2.state === "INITIAL" && !prevState2.isPending && nextState.isPending && !nextState.isRetrying) {
466
+ return;
467
+ }
468
+ const paths = compressPaths(usedPathsRef.current);
469
+ for (const path of paths) {
470
+ const rootKey = path[0];
471
+ if (!changedKeys.includes(rootKey)) continue;
472
+ const prevVal = getValueByPath(prevState2, path);
473
+ const nextVal = getValueByPath(nextState, path);
474
+ if (!Object.is(prevVal, nextVal)) {
475
+ snapshotRef.current = nextState;
476
+ return onStoreChange();
477
+ }
478
+ }
479
+ });
480
+ },
481
+ // False positive (missing dependency: 'usedPathsRef'): usedPathsRef is a ref, no need to add to deps
482
+ // eslint-disable-next-line react-hooks/exhaustive-deps
483
+ [store]
484
+ ),
485
+ getSnapshot,
486
+ getSnapshot
487
+ );
463
488
  useEffect(() => {
464
489
  if (revalidateOnMount !== false) {
465
490
  if (!initialDataIsStale) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "yuustate",
3
3
  "description": "Lightweight unified state management for sync and async data.",
4
4
  "private": false,
5
- "version": "3.9.0",
5
+ "version": "3.10.1",
6
6
  "keywords": [
7
7
  "utilities",
8
8
  "store",
@@ -70,8 +70,8 @@
70
70
  },
71
71
  "packageManager": "pnpm@10.32.1",
72
72
  "peerDependencies": {
73
- "@types/react": ">=17.0",
74
- "react": ">=17.0"
73
+ "@types/react": ">=18.0",
74
+ "react": ">=18.0"
75
75
  },
76
76
  "peerDependenciesMeta": {
77
77
  "@types/react": {
@@ -350,7 +350,7 @@ export declare const createQuery: <TData, TVariable extends StoreKey = never, TE
350
350
  */
351
351
  initialDataIsStale?: boolean;
352
352
  } | {
353
- initialData?: never;
353
+ initialData?: never | undefined;
354
354
  initialDataIsStale?: never;
355
355
  })) => QueryState<TData, TError>) & {
356
356
  setState: (value: SetStateInput<QueryState<TData, TError>>) => void;
@@ -31,5 +31,5 @@ export declare const createStore: <TState extends Record<string, any>>(initialSt
31
31
  /**
32
32
  * Initial state used on first render (and will also update the store state right after that)
33
33
  */
34
- initialState?: Partial<TState>;
34
+ initialState?: Partial<TState> | undefined;
35
35
  }) => TState) & import("../vanilla.ts").StoreApi<TState>;
@@ -4,7 +4,6 @@ export declare const getValueByPath: (obj: any, path: Path) => any;
4
4
  export declare const isPrefixPath: (candidatePrefix: Path, targetPath: Path) => boolean;
5
5
  export declare const compressPaths: (paths: Path[]) => Path[];
6
6
  export declare const useStoreStateProxy: <TState extends Record<string, any>>(storeState: TState) => readonly [TState, import("react").RefObject<Path[]>];
7
- export declare const NO_INITIAL_VALUE: {};
8
7
  export declare const useStoreStateWithInitializer: <TState extends Record<string, any>>(store: StoreApi<TState>, initialState?: Partial<TState>) => readonly [TState, import("react").RefObject<WeakMap<StoreApi<TState>, number>>];
9
8
  /**
10
9
  * React hook for subscribing to a store using automatic dependency tracking.
@@ -29,6 +28,6 @@ export declare const useStoreStateWithInitializer: <TState extends Record<string
29
28
  * // Component will only re-render if `user.name` changes
30
29
  */
31
30
  export declare const useStoreState: <TState extends Record<string, any>>(store: StoreApi<TState>, options?: {
32
- initialState?: Partial<TState>;
31
+ initialState?: Partial<TState> | undefined;
33
32
  }) => TState;
34
33
  export {};
package/react.js CHANGED
@@ -51,34 +51,46 @@ const useStoreStateProxy = (storeState) => {
51
51
  }, [storeState]);
52
52
  return [trackedState, usedPathsRef];
53
53
  };
54
- const NO_INITIAL_VALUE = {};
55
- const useStoreStateWithInitializer = (store, initialState = NO_INITIAL_VALUE) => {
54
+ const useStoreStateWithInitializer = (store, initialState) => {
56
55
  const initiatedAt = react.useRef(new WeakMap([[store, 0]]));
57
56
  react.useEffect(() => {
58
- if (initialState === NO_INITIAL_VALUE || initiatedAt.current.get(store)) return;
57
+ if (initialState === void 0 || initiatedAt.current.get(store)) return;
59
58
  store.setState(initialState);
60
59
  initiatedAt.current.set(store, Date.now());
61
60
  }, [store, initialState]);
62
61
  const storeState = store.getState();
63
- const finalState = initialState === NO_INITIAL_VALUE || initiatedAt.current.get(store) ? storeState : { ...storeState, ...initialState };
62
+ const finalState = initialState === void 0 || initiatedAt.current.get(store) ? storeState : { ...storeState, ...initialState };
64
63
  return [finalState, initiatedAt];
65
64
  };
66
65
  const useStoreState = (store, options = {}) => {
67
66
  const [state] = useStoreStateWithInitializer(store, options.initialState);
68
67
  const [trackedState, usedPathsRef] = useStoreStateProxy(state);
69
- const [, reRender] = react.useState({});
70
- react.useEffect(() => {
71
- return store.subscribe((nextState, prevState, changedKeys) => {
72
- const paths = compressPaths(usedPathsRef.current);
73
- for (const path of paths) {
74
- const rootKey = path[0];
75
- if (!changedKeys.includes(rootKey)) continue;
76
- const prevVal = getValueByPath(prevState, path);
77
- const nextVal = getValueByPath(nextState, path);
78
- if (!Object.is(prevVal, nextVal)) return reRender({});
79
- }
80
- });
81
- }, [store]);
68
+ const snapshotRef = react.useRef(store.getState());
69
+ const getSnapshot = () => snapshotRef.current;
70
+ react.useSyncExternalStore(
71
+ react.useCallback(
72
+ (onStoreChange) => {
73
+ return store.subscribe((nextState, prevState, changedKeys) => {
74
+ const paths = compressPaths(usedPathsRef.current);
75
+ for (const path of paths) {
76
+ const rootKey = path[0];
77
+ if (!changedKeys.includes(rootKey)) continue;
78
+ const prevVal = getValueByPath(prevState, path);
79
+ const nextVal = getValueByPath(nextState, path);
80
+ if (!Object.is(prevVal, nextVal)) {
81
+ snapshotRef.current = nextState;
82
+ return onStoreChange();
83
+ }
84
+ }
85
+ });
86
+ },
87
+ // False positive (missing dependency: 'usedPathsRef'): usedPathsRef is a ref, no need to add to deps
88
+ // eslint-disable-next-line react-hooks/exhaustive-deps
89
+ [store]
90
+ ),
91
+ getSnapshot,
92
+ getSnapshot
93
+ );
82
94
  return trackedState;
83
95
  };
84
96
 
@@ -415,14 +427,14 @@ const createQuery = (queryFn, options = {}) => {
415
427
  }
416
428
  const useStore = (options2 = {}) => {
417
429
  const {
418
- initialData = NO_INITIAL_VALUE,
430
+ initialData,
419
431
  initialDataIsStale = false,
420
432
  revalidateOnMount = true,
421
433
  keepPreviousData
422
434
  } = options2;
423
435
  const [storeState, initialDataInitiatedAt] = useStoreStateWithInitializer(
424
436
  store,
425
- initialData === NO_INITIAL_VALUE ? void 0 : {
437
+ initialData === void 0 ? void 0 : {
426
438
  state: "SUCCESS",
427
439
  isSuccess: true,
428
440
  data: initialData
@@ -446,22 +458,35 @@ const createQuery = (queryFn, options = {}) => {
446
458
  { ...storeStateToBeUsed, isPending: true }
447
459
  ) : storeStateToBeUsed
448
460
  );
449
- const [, reRender] = react.useState({});
450
- react.useEffect(() => {
451
- return store.subscribe((nextState, prevState2, changedKeys) => {
452
- if (prevState2.state === "INITIAL" && !prevState2.isPending && nextState.isPending && !nextState.isRetrying) {
453
- return;
454
- }
455
- const paths = compressPaths(usedPathsRef.current);
456
- for (const path of paths) {
457
- const rootKey = path[0];
458
- if (!changedKeys.includes(rootKey)) continue;
459
- const prevVal = getValueByPath(prevState2, path);
460
- const nextVal = getValueByPath(nextState, path);
461
- if (!Object.is(prevVal, nextVal)) return reRender({});
462
- }
463
- });
464
- }, [store]);
461
+ const snapshotRef = react.useRef(store.getState());
462
+ const getSnapshot = () => snapshotRef.current;
463
+ react.useSyncExternalStore(
464
+ react.useCallback(
465
+ (onStoreChange) => {
466
+ return store.subscribe((nextState, prevState2, changedKeys) => {
467
+ if (prevState2.state === "INITIAL" && !prevState2.isPending && nextState.isPending && !nextState.isRetrying) {
468
+ return;
469
+ }
470
+ const paths = compressPaths(usedPathsRef.current);
471
+ for (const path of paths) {
472
+ const rootKey = path[0];
473
+ if (!changedKeys.includes(rootKey)) continue;
474
+ const prevVal = getValueByPath(prevState2, path);
475
+ const nextVal = getValueByPath(nextState, path);
476
+ if (!Object.is(prevVal, nextVal)) {
477
+ snapshotRef.current = nextState;
478
+ return onStoreChange();
479
+ }
480
+ }
481
+ });
482
+ },
483
+ // False positive (missing dependency: 'usedPathsRef'): usedPathsRef is a ref, no need to add to deps
484
+ // eslint-disable-next-line react-hooks/exhaustive-deps
485
+ [store]
486
+ ),
487
+ getSnapshot,
488
+ getSnapshot
489
+ );
465
490
  react.useEffect(() => {
466
491
  if (revalidateOnMount !== false) {
467
492
  if (!initialDataIsStale) {