@syncular/react 0.32.0 → 0.32.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.
@@ -3,4 +3,4 @@
3
3
  * Vite HMR uses it to replace a worker owner after a Syncular package upgrade
4
4
  * even when the application's generated schema version did not change.
5
5
  */
6
- export declare const SYNCULAR_REACT_RUNTIME_VERSION = "0.32.0";
6
+ export declare const SYNCULAR_REACT_RUNTIME_VERSION = "0.32.1";
@@ -3,4 +3,4 @@
3
3
  * Vite HMR uses it to replace a worker owner after a Syncular package upgrade
4
4
  * even when the application's generated schema version did not change.
5
5
  */
6
- export const SYNCULAR_REACT_RUNTIME_VERSION = '0.32.0';
6
+ export const SYNCULAR_REACT_RUNTIME_VERSION = '0.32.1';
@@ -13,27 +13,42 @@ export function useDiagnostics(options = {}) {
13
13
  return expectedSubscriptions.length === 0 ? {} : { expectedSubscriptions };
14
14
  }, [requestKey]);
15
15
  const generation = useRef(0);
16
- const [snapshot, setSnapshot] = useState();
17
- const [isLoading, setIsLoading] = useState(true);
18
- const [error, setError] = useState();
16
+ const [state, setState] = useState({
17
+ client,
18
+ requestKey,
19
+ snapshot: undefined,
20
+ error: undefined,
21
+ settled: false,
22
+ });
19
23
  const refresh = useCallback(() => {
20
24
  const current = ++generation.current;
21
- setIsLoading(true);
25
+ setState((prev) => (prev.settled ? { ...prev, settled: false } : prev));
22
26
  void Promise.resolve()
23
27
  .then(() => client.diagnosticsSnapshot(request))
24
28
  .then((next) => {
25
29
  if (generation.current !== current)
26
30
  return;
27
- setSnapshot(next);
28
- setError(undefined);
29
- setIsLoading(false);
31
+ setState({
32
+ client,
33
+ requestKey,
34
+ snapshot: next,
35
+ error: undefined,
36
+ settled: true,
37
+ });
30
38
  }, (reason) => {
31
39
  if (generation.current !== current)
32
40
  return;
33
- setError(reason instanceof Error ? reason : new Error(String(reason)));
34
- setIsLoading(false);
41
+ setState((prev) => ({
42
+ client,
43
+ requestKey,
44
+ snapshot: prev.client === client && prev.requestKey === requestKey
45
+ ? prev.snapshot
46
+ : undefined,
47
+ error: reason instanceof Error ? reason : new Error(String(reason)),
48
+ settled: true,
49
+ }));
35
50
  });
36
- }, [client, request]);
51
+ }, [client, request, requestKey]);
37
52
  useEffect(() => {
38
53
  const unsubscribe = client.onDiagnostics(refresh);
39
54
  refresh();
@@ -42,5 +57,11 @@ export function useDiagnostics(options = {}) {
42
57
  unsubscribe();
43
58
  };
44
59
  }, [client, refresh]);
45
- return { snapshot, isLoading, error, refresh };
60
+ const current = state.client === client && state.requestKey === requestKey;
61
+ return {
62
+ snapshot: current ? state.snapshot : undefined,
63
+ isLoading: !current || !state.settled,
64
+ error: current ? state.error : undefined,
65
+ refresh,
66
+ };
46
67
  }
@@ -53,7 +53,7 @@ export function useWindow(base) {
53
53
  const stableBase = useMemo(() => base, [baseIdentity]);
54
54
  const entry = useMemo(() => store.window(stableBase), [store, stableBase]);
55
55
  const state = useSyncExternalStore(entry.subscribe, entry.getSnapshot, entry.getSnapshot);
56
- useEffect(() => () => store.releaseWindowClaims(owner.current), [store]);
56
+ useEffect(() => () => store.releaseWindowClaims(owner.current), [stableBase, store]);
57
57
  const setWindow = useCallback((units) => store.setWindowClaim(owner.current, stableBase, units), [stableBase, store]);
58
58
  const isComplete = useCallback((unit) => windowComplete(state, unit), [state]);
59
59
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/react",
3
- "version": "0.32.0",
3
+ "version": "0.32.1",
4
4
  "description": "React hooks for Syncular offline-first sync",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -55,15 +55,15 @@
55
55
  "test": "bun test --preload ./test/setup.ts"
56
56
  },
57
57
  "dependencies": {
58
- "@syncular/client": "0.32.0"
58
+ "@syncular/client": "0.32.1"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "react": ">=18.0.0"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@happy-dom/global-registrator": "^20.0.0",
65
- "@syncular/core": "0.32.0",
66
- "@syncular/server": "0.32.0",
65
+ "@syncular/core": "0.32.1",
66
+ "@syncular/server": "0.32.1",
67
67
  "@testing-library/react": "^16.1.0",
68
68
  "@types/react": "^18.3.0",
69
69
  "@types/react-dom": "^18.3.0",
@@ -3,4 +3,4 @@
3
3
  * Vite HMR uses it to replace a worker owner after a Syncular package upgrade
4
4
  * even when the application's generated schema version did not change.
5
5
  */
6
- export const SYNCULAR_REACT_RUNTIME_VERSION = '0.32.0';
6
+ export const SYNCULAR_REACT_RUNTIME_VERSION = '0.32.1';
@@ -38,33 +38,51 @@ export function useDiagnostics(
38
38
  return expectedSubscriptions.length === 0 ? {} : { expectedSubscriptions };
39
39
  }, [requestKey]);
40
40
  const generation = useRef(0);
41
- const [snapshot, setSnapshot] = useState<
42
- ClientDiagnosticsSnapshot | undefined
43
- >();
44
- const [isLoading, setIsLoading] = useState(true);
45
- const [error, setError] = useState<Error | undefined>();
41
+ const [state, setState] = useState<{
42
+ readonly client: typeof client;
43
+ readonly requestKey: string;
44
+ readonly snapshot: ClientDiagnosticsSnapshot | undefined;
45
+ readonly error: Error | undefined;
46
+ readonly settled: boolean;
47
+ }>({
48
+ client,
49
+ requestKey,
50
+ snapshot: undefined,
51
+ error: undefined,
52
+ settled: false,
53
+ });
46
54
 
47
55
  const refresh = useCallback(() => {
48
56
  const current = ++generation.current;
49
- setIsLoading(true);
57
+ setState((prev) => (prev.settled ? { ...prev, settled: false } : prev));
50
58
  void Promise.resolve()
51
59
  .then(() => client.diagnosticsSnapshot(request))
52
60
  .then(
53
61
  (next) => {
54
62
  if (generation.current !== current) return;
55
- setSnapshot(next);
56
- setError(undefined);
57
- setIsLoading(false);
63
+ setState({
64
+ client,
65
+ requestKey,
66
+ snapshot: next,
67
+ error: undefined,
68
+ settled: true,
69
+ });
58
70
  },
59
71
  (reason: unknown) => {
60
72
  if (generation.current !== current) return;
61
- setError(
62
- reason instanceof Error ? reason : new Error(String(reason)),
63
- );
64
- setIsLoading(false);
73
+ setState((prev) => ({
74
+ client,
75
+ requestKey,
76
+ snapshot:
77
+ prev.client === client && prev.requestKey === requestKey
78
+ ? prev.snapshot
79
+ : undefined,
80
+ error: reason instanceof Error ? reason : new Error(String(reason)),
81
+ settled: true,
82
+ }));
65
83
  },
66
84
  );
67
- }, [client, request]);
85
+ }, [client, request, requestKey]);
68
86
 
69
87
  useEffect(() => {
70
88
  const unsubscribe = client.onDiagnostics(refresh);
@@ -75,5 +93,11 @@ export function useDiagnostics(
75
93
  };
76
94
  }, [client, refresh]);
77
95
 
78
- return { snapshot, isLoading, error, refresh };
96
+ const current = state.client === client && state.requestKey === requestKey;
97
+ return {
98
+ snapshot: current ? state.snapshot : undefined,
99
+ isLoading: !current || !state.settled,
100
+ error: current ? state.error : undefined,
101
+ refresh,
102
+ };
79
103
  }
package/src/use-window.ts CHANGED
@@ -97,7 +97,10 @@ export function useWindow(base: WindowBase): UseWindowResult {
97
97
  entry.getSnapshot,
98
98
  );
99
99
 
100
- useEffect(() => () => store.releaseWindowClaims(owner.current), [store]);
100
+ useEffect(
101
+ () => () => store.releaseWindowClaims(owner.current),
102
+ [stableBase, store],
103
+ );
101
104
 
102
105
  const setWindow = useCallback(
103
106
  (units: readonly string[]) =>