react-mirrorstate 0.2.3 → 0.3.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.
@@ -4,12 +4,13 @@ declare class WebSocketConnectionManager {
4
4
  private isConnecting;
5
5
  private listeners;
6
6
  private currentStates;
7
+ private lastSeq;
8
+ private queuedUpdates;
7
9
  private getWebSocketConfig;
8
10
  private buildWebSocketURL;
9
11
  private cleanup;
10
12
  connect(): Promise<void>;
11
13
  subscribe(name: string, listener: StateListener): () => void;
12
- private lastSentState;
13
14
  private pendingUpdates;
14
15
  updateState(name: string, state: any): void;
15
16
  getCurrentState(name: string): any;
@@ -3,6 +3,8 @@ class WebSocketConnectionManager {
3
3
  isConnecting = false;
4
4
  listeners = new Map();
5
5
  currentStates = new Map();
6
+ lastSeq = new Map();
7
+ queuedUpdates = new Map();
6
8
  async getWebSocketConfig() {
7
9
  try {
8
10
  const config = await import("virtual:mirrorstate/config");
@@ -40,28 +42,36 @@ class WebSocketConnectionManager {
40
42
  this.ws = new WebSocket(wsUrl);
41
43
  this.ws.onopen = () => {
42
44
  this.isConnecting = false;
45
+ // Flush any queued updates
46
+ this.queuedUpdates.forEach((state, name) => {
47
+ this.updateState(name, state);
48
+ });
49
+ this.queuedUpdates.clear();
43
50
  };
44
51
  this.ws.onclose = () => {
45
52
  this.cleanup();
46
53
  };
47
- this.ws.onerror = () => {
48
- console.error("WebSocket error");
54
+ this.ws.onerror = (e) => {
55
+ console.error("WebSocket error", e);
49
56
  this.cleanup();
50
57
  };
51
58
  this.ws.onmessage = (event) => {
59
+ let data;
52
60
  try {
53
- const data = JSON.parse(event.data);
54
- if (data.type === "initialState") {
55
- this.currentStates.set(data.name, data.state);
56
- this.notifyListeners(data.name, data.state);
57
- }
58
- if (data.type === "fileChange") {
59
- this.currentStates.set(data.name, data.state);
60
- this.notifyListeners(data.name, data.state);
61
- }
61
+ data = JSON.parse(event.data);
62
62
  }
63
63
  catch (error) {
64
64
  console.error("Error handling server message:", error);
65
+ return;
66
+ }
67
+ if (data.type === "fileChange") {
68
+ // Only apply updates with a higher sequence number
69
+ const currentSeq = this.lastSeq.get(data.name) ?? -1;
70
+ if (data.seq !== undefined && data.seq > currentSeq) {
71
+ this.lastSeq.set(data.name, data.seq);
72
+ this.currentStates.set(data.name, data.state);
73
+ this.notifyListeners(data.name, data.state);
74
+ }
65
75
  }
66
76
  };
67
77
  }
@@ -70,12 +80,7 @@ class WebSocketConnectionManager {
70
80
  this.listeners.set(name, new Set());
71
81
  }
72
82
  this.listeners.get(name).add(listener);
73
- // Connect if not already connected (dev mode)
74
83
  this.connect();
75
- // If we already have state for this name, notify immediately
76
- if (this.currentStates.has(name)) {
77
- listener(this.currentStates.get(name));
78
- }
79
84
  return () => {
80
85
  const nameListeners = this.listeners.get(name);
81
86
  if (nameListeners) {
@@ -86,10 +91,10 @@ class WebSocketConnectionManager {
86
91
  }
87
92
  };
88
93
  }
89
- lastSentState = new Map();
90
94
  pendingUpdates = new Map();
91
95
  updateState(name, state) {
92
96
  if (this.ws?.readyState !== WebSocket.OPEN) {
97
+ this.queuedUpdates.set(name, state);
93
98
  return;
94
99
  }
95
100
  // Cancel any pending update for this state name
@@ -97,19 +102,16 @@ class WebSocketConnectionManager {
97
102
  if (pendingUpdate) {
98
103
  clearTimeout(pendingUpdate);
99
104
  }
100
- // Check if this is actually a different state
101
- const lastState = this.lastSentState.get(name);
102
- if (lastState === state) {
103
- return;
104
- }
105
105
  // Debounce rapid updates
106
106
  const timeout = setTimeout(() => {
107
107
  if (!this.ws) {
108
108
  return;
109
109
  }
110
- this.ws.send(JSON.stringify({ name, state }));
110
+ this.ws.send(JSON.stringify({
111
+ name,
112
+ state,
113
+ }));
111
114
  this.currentStates.set(name, state);
112
- this.lastSentState.set(name, state);
113
115
  this.pendingUpdates.delete(name);
114
116
  }, 10);
115
117
  this.pendingUpdates.set(name, timeout);
package/dist/index.js CHANGED
@@ -3,7 +3,9 @@ import { produce } from "immer";
3
3
  import { connectionManager } from "./connection-manager";
4
4
  import { INITIAL_STATES } from "virtual:mirrorstate/initial-states";
5
5
  export function useMirrorState(name, initialValue) {
6
- const [state, setState] = useState(() => INITIAL_STATES?.[name] ?? initialValue);
6
+ // Capture initialValue once on first render to make it stable
7
+ const initialValueRef = useRef(initialValue);
8
+ const [state, setState] = useState(() => INITIAL_STATES?.[name] ?? initialValueRef.current);
7
9
  const hasCreatedFile = useRef(false);
8
10
  useEffect(() => {
9
11
  // Subscribe to state changes for this name
@@ -12,21 +14,20 @@ export function useMirrorState(name, initialValue) {
12
14
  });
13
15
  // If file doesn't exist and initialValue was provided, create it
14
16
  if (INITIAL_STATES?.[name] === undefined &&
15
- initialValue !== undefined &&
17
+ initialValueRef.current !== undefined &&
16
18
  !hasCreatedFile.current) {
17
19
  hasCreatedFile.current = true;
18
- connectionManager.updateState(name, initialValue);
20
+ connectionManager.updateState(name, initialValueRef.current);
19
21
  }
20
22
  return () => {
21
23
  unsubscribe();
22
24
  };
23
- }, [name, initialValue]);
25
+ }, [name]);
24
26
  const updateMirrorState = (updater) => {
25
- setState((prevState) => {
26
- const newState = produce(prevState, updater);
27
- connectionManager.updateState(name, newState);
28
- return newState;
29
- });
27
+ const currentState = connectionManager.getCurrentState(name) ?? state;
28
+ const newState = produce(currentState, updater);
29
+ connectionManager.updateState(name, newState);
30
+ setState(newState);
30
31
  };
31
32
  return [state, updateMirrorState];
32
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-mirrorstate",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "description": "React library for bidirectional state synchronization with MirrorState",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",