@pluv/react 0.18.0 → 0.19.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.
@@ -1,18 +1,18 @@
1
1
 
2
- > @pluv/react@0.18.0 build /home/runner/work/pluv/pluv/packages/react
2
+ > @pluv/react@0.19.0 build /home/runner/work/pluv/pluv/packages/react
3
3
  > tsup src/index.ts --format esm,cjs --dts --external react
4
4
 
5
- CLI Building entry: src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.0.2
8
- CLI Target: es6
9
- ESM Build start
10
- CJS Build start
11
- CJS dist/index.js 12.77 KB
12
- CJS ⚡️ Build success in 100ms
13
- ESM dist/index.mjs 10.18 KB
14
- ESM ⚡️ Build success in 100ms
15
- DTS Build start
16
- DTS ⚡️ Build success in 3856ms
17
- DTS dist/index.d.mts 5.38 KB
18
- DTS dist/index.d.ts 5.38 KB
5
+ CLI Building entry: src/index.ts
6
+ CLI Using tsconfig: tsconfig.json
7
+ CLI tsup v8.1.0
8
+ CLI Target: es6
9
+ ESM Build start
10
+ CJS Build start
11
+ ESM dist/index.mjs 11.61 KB
12
+ ESM ⚡️ Build success in 85ms
13
+ CJS dist/index.js 14.02 KB
14
+ CJS ⚡️ Build success in 86ms
15
+ DTS Build start
16
+ DTS ⚡️ Build success in 4322ms
17
+ DTS dist/index.d.mts 5.35 KB
18
+ DTS dist/index.d.ts 5.35 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @pluv/react
2
2
 
3
+ ## 0.19.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 798e32d: **Breaking**: useOthers now allows returning any data besides arrays when using a custom selector.
8
+ - 8450306: **Breaking**: Require minimum react version of ^18.0.0.
9
+
10
+ ### Patch Changes
11
+
12
+ - 607afe3: Update the `updateMyPresence` function from `useMyPresence` to simply forward the function from the `PluvRoom`. This means all `updateMyPresence` functions should be the same underlying reference across all `useMyPresence` hooks.
13
+ - Updated dependencies [137444b]
14
+ - Updated dependencies [f5e4370]
15
+ - @pluv/client@0.19.0
16
+ - @pluv/crdt@0.19.0
17
+ - @pluv/types@0.19.0
18
+
3
19
  ## 0.18.0
4
20
 
5
21
  ### Minor Changes
package/dist/index.d.mts CHANGED
@@ -33,7 +33,7 @@ interface SubscriptionHookOptions<T extends unknown> {
33
33
  }
34
34
  type UpdateMyPresenceAction<TPresence extends JsonObject> = Partial<TPresence> | ((oldPresence: TPresence | null) => Partial<TPresence>);
35
35
  type BroadcastProxy<TIO extends IOLike> = (<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => void) & {
36
- [event in keyof InferIOInput<TIO>]: (callback: Id<InferIOInput<TIO>[event]>) => void;
36
+ [event in keyof InferIOInput<TIO>]: (input: Id<InferIOInput<TIO>[event]>) => void;
37
37
  };
38
38
  type EventProxy<TIO extends IOLike> = {
39
39
  [event in keyof InferIOOutput<TIO>]: {
@@ -52,8 +52,8 @@ interface CreateRoomBundle<TIO extends IOLike, TPresence extends JsonObject, TSt
52
52
  useEvent: <TType extends keyof InferIOOutput<TIO>>(type: TType, callback: (data: Id<IOEventMessage<TIO, TType>>) => void) => void;
53
53
  useMyPresence: <T extends unknown = TPresence>(selector?: (myPresence: TPresence) => T, options?: SubscriptionHookOptions<Id<T> | null>) => [myPresence: Id<T>, updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>];
54
54
  useMyself: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
55
- useOther: <T extends unknown = UserInfo<TIO, TPresence>>(connectionId: string, selector?: (other: UserInfo<TIO, TPresence>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
56
- useOthers: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T[], options?: SubscriptionHookOptions<readonly Id<T>[]>) => readonly Id<T>[];
55
+ useOther: <T extends unknown = UserInfo<TIO, TPresence>>(connectionId: string, selector?: (other: UserInfo<TIO, TPresence>) => T, options?: SubscriptionHookOptions<T | null>) => T | null;
56
+ useOthers: <T extends unknown = readonly UserInfo<TIO, TPresence>[]>(selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T, options?: SubscriptionHookOptions<T>) => T;
57
57
  useRedo: () => () => void;
58
58
  useRoom: () => AbstractRoom<TIO, TPresence, TStorage>;
59
59
  useStorage: <TKey extends keyof TStorage, TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>>(key: TKey, selector?: (data: InferCrdtStorageJson<TStorage[TKey]>) => TData, options?: SubscriptionHookOptions<TData | null>) => [data: TData | null, sharedType: TStorage[TKey] | null];
package/dist/index.d.ts CHANGED
@@ -33,7 +33,7 @@ interface SubscriptionHookOptions<T extends unknown> {
33
33
  }
34
34
  type UpdateMyPresenceAction<TPresence extends JsonObject> = Partial<TPresence> | ((oldPresence: TPresence | null) => Partial<TPresence>);
35
35
  type BroadcastProxy<TIO extends IOLike> = (<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => void) & {
36
- [event in keyof InferIOInput<TIO>]: (callback: Id<InferIOInput<TIO>[event]>) => void;
36
+ [event in keyof InferIOInput<TIO>]: (input: Id<InferIOInput<TIO>[event]>) => void;
37
37
  };
38
38
  type EventProxy<TIO extends IOLike> = {
39
39
  [event in keyof InferIOOutput<TIO>]: {
@@ -52,8 +52,8 @@ interface CreateRoomBundle<TIO extends IOLike, TPresence extends JsonObject, TSt
52
52
  useEvent: <TType extends keyof InferIOOutput<TIO>>(type: TType, callback: (data: Id<IOEventMessage<TIO, TType>>) => void) => void;
53
53
  useMyPresence: <T extends unknown = TPresence>(selector?: (myPresence: TPresence) => T, options?: SubscriptionHookOptions<Id<T> | null>) => [myPresence: Id<T>, updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>];
54
54
  useMyself: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
55
- useOther: <T extends unknown = UserInfo<TIO, TPresence>>(connectionId: string, selector?: (other: UserInfo<TIO, TPresence>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
56
- useOthers: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T[], options?: SubscriptionHookOptions<readonly Id<T>[]>) => readonly Id<T>[];
55
+ useOther: <T extends unknown = UserInfo<TIO, TPresence>>(connectionId: string, selector?: (other: UserInfo<TIO, TPresence>) => T, options?: SubscriptionHookOptions<T | null>) => T | null;
56
+ useOthers: <T extends unknown = readonly UserInfo<TIO, TPresence>[]>(selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T, options?: SubscriptionHookOptions<T>) => T;
57
57
  useRedo: () => () => void;
58
58
  useRoom: () => AbstractRoom<TIO, TPresence, TStorage>;
59
59
  useStorage: <TKey extends keyof TStorage, TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>>(key: TKey, selector?: (data: InferCrdtStorageJson<TStorage[TKey]>) => TData, options?: SubscriptionHookOptions<TData | null>) => [data: TData | null, sharedType: TStorage[TKey] | null];
package/dist/index.js CHANGED
@@ -38,25 +38,39 @@ module.exports = __toCommonJS(src_exports);
38
38
  var import_client2 = require("@pluv/client");
39
39
 
40
40
  // src/createBundle.tsx
41
- var import_react3 = require("react");
41
+ var import_react5 = require("react");
42
42
 
43
43
  // src/createRoomBundle.tsx
44
44
  var import_client = require("@pluv/client");
45
45
  var import_crdt = require("@pluv/crdt");
46
46
  var import_fast_deep_equal = __toESM(require("fast-deep-equal"));
47
- var import_react2 = require("react");
47
+ var import_react4 = require("react");
48
48
 
49
- // src/internal/hooks/useRerender.ts
49
+ // src/internal/hooks/useNoSsr.ts
50
50
  var import_react = require("react");
51
+ var useNoSsr = () => {
52
+ const [didMount, setDidMount] = (0, import_react.useState)(false);
53
+ (0, import_react.useEffect)(() => setDidMount(typeof window !== "undefined"), []);
54
+ const noSsr = (0, import_react.useCallback)(
55
+ (value, fallback = null) => {
56
+ return didMount ? typeof value !== "function" ? value : value() : fallback;
57
+ },
58
+ [didMount]
59
+ );
60
+ return noSsr;
61
+ };
62
+
63
+ // src/internal/hooks/useRerender.ts
64
+ var import_react2 = require("react");
51
65
  var useRerender = () => {
52
- const [, setCount] = (0, import_react.useState)(0);
53
- return (0, import_react.useCallback)(() => {
66
+ const [, setCount] = (0, import_react2.useState)(0);
67
+ return (0, import_react2.useCallback)(() => {
54
68
  setCount((oldCount) => (oldCount + 1) % Number.MAX_SAFE_INTEGER);
55
69
  }, []);
56
70
  };
57
71
 
58
72
  // src/internal/hooks/useSyncExternalStoreWithSelector.ts
59
- var import_with_selector = require("use-sync-external-store/with-selector.js");
73
+ var import_react3 = require("react");
60
74
 
61
75
  // src/internal/utils/identity.ts
62
76
  var identity = (x) => x;
@@ -66,16 +80,40 @@ var shallowArrayEqual = (a, b) => {
66
80
  return a.length === b.length && a.every((item, i) => item === b[i]);
67
81
  };
68
82
 
83
+ // src/internal/utils/shallowEqual.ts
84
+ var shallowEqual = (a, b) => a === b;
85
+
86
+ // src/internal/hooks/useSyncExternalStoreWithSelector.ts
87
+ var useSyncExternalStoreWithSelector = (subscribe, getClientSnapshot, getServerSnapshot, selector, isEqual) => {
88
+ const noSsr = useNoSsr();
89
+ const cacheRef = (0, import_react3.useRef)(null);
90
+ const _getServerSnapshot = getServerSnapshot ? () => {
91
+ const data = getServerSnapshot();
92
+ return selector(data);
93
+ } : void 0;
94
+ const _getClientSnapshot = () => {
95
+ const data = getClientSnapshot();
96
+ const snapshot = selector(data);
97
+ const compare = isEqual != null ? isEqual : shallowEqual;
98
+ const cached = cacheRef.current;
99
+ const isUnchanged = cached === null ? snapshot === null : compare(cached, snapshot);
100
+ cacheRef.current = isUnchanged ? cached != null ? cached : snapshot : snapshot;
101
+ return cacheRef.current;
102
+ };
103
+ const result = (0, import_react3.useSyncExternalStore)(subscribe, _getClientSnapshot, _getServerSnapshot);
104
+ return typeof window !== "undefined" ? noSsr(result, _getServerSnapshot == null ? void 0 : _getServerSnapshot()) : result;
105
+ };
106
+
69
107
  // src/createRoomBundle.tsx
70
108
  var import_jsx_runtime = require("react/jsx-runtime");
71
109
  var createRoomBundle = (client, options) => {
72
110
  var _a;
73
111
  const _initialStorage = (_a = options.initialStorage) != null ? _a : import_crdt.noop.doc();
74
- const PluvRoomContext = (0, import_react2.createContext)(null);
75
- const MockedRoomContext = (0, import_react2.createContext)({ room: null });
76
- const MockedRoomProvider = (0, import_react2.memo)((props) => {
112
+ const PluvRoomContext = (0, import_react4.createContext)(null);
113
+ const MockedRoomContext = (0, import_react4.createContext)({ room: null });
114
+ const MockedRoomProvider = (0, import_react4.memo)((props) => {
77
115
  const { children, events, initialPresence, initialStorage, room: _room } = props;
78
- const [room] = (0, import_react2.useState)(() => {
116
+ const [room] = (0, import_react4.useState)(() => {
79
117
  return new import_client.MockedRoom(_room, {
80
118
  events,
81
119
  initialPresence,
@@ -86,11 +124,11 @@ var createRoomBundle = (client, options) => {
86
124
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MockedRoomContext.Provider, { value: { room }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PluvRoomContext.Provider, { value: room, children }) });
87
125
  });
88
126
  MockedRoomProvider.displayName = "MockedRoomProvider";
89
- const PluvRoomProvider = (0, import_react2.memo)((props) => {
127
+ const PluvRoomProvider = (0, import_react4.memo)((props) => {
90
128
  const { children, debug, initialPresence, initialStorage, onAuthorizationFail, room: _room } = props;
91
129
  const rerender = useRerender();
92
- const { room: mockedRoom } = (0, import_react2.useContext)(MockedRoomContext);
93
- const [room] = (0, import_react2.useState)(() => {
130
+ const { room: mockedRoom } = (0, import_react4.useContext)(MockedRoomContext);
131
+ const [room] = (0, import_react4.useState)(() => {
94
132
  return client.createRoom(_room, {
95
133
  addons: options.addons,
96
134
  debug,
@@ -100,7 +138,7 @@ var createRoomBundle = (client, options) => {
100
138
  onAuthorizationFail
101
139
  });
102
140
  });
103
- (0, import_react2.useEffect)(() => {
141
+ (0, import_react4.useEffect)(() => {
104
142
  const unsubscribe = room.subscribe("connection", () => {
105
143
  rerender();
106
144
  });
@@ -108,7 +146,7 @@ var createRoomBundle = (client, options) => {
108
146
  unsubscribe();
109
147
  };
110
148
  }, [rerender, room]);
111
- (0, import_react2.useEffect)(() => {
149
+ (0, import_react4.useEffect)(() => {
112
150
  client.enter(room);
113
151
  return () => {
114
152
  client.leave(room);
@@ -117,16 +155,16 @@ var createRoomBundle = (client, options) => {
117
155
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PluvRoomContext.Provider, { value: mockedRoom != null ? mockedRoom : room, children });
118
156
  });
119
157
  PluvRoomProvider.displayName = "PluvRoomProvider";
120
- const useRoom = () => (0, import_react2.useContext)(PluvRoomContext);
158
+ const useRoom = () => (0, import_react4.useContext)(PluvRoomContext);
121
159
  const useBroadcast = () => {
122
160
  const room = useRoom();
123
- const broadcast = (0, import_react2.useCallback)(
161
+ const broadcast = (0, import_react4.useCallback)(
124
162
  (event2, data) => {
125
163
  room.broadcast(event2, data);
126
164
  },
127
165
  [room]
128
166
  );
129
- return (0, import_react2.useMemo)(() => {
167
+ return (0, import_react4.useMemo)(() => {
130
168
  return new Proxy(broadcast, {
131
169
  get(fn, prop) {
132
170
  return (data) => {
@@ -138,30 +176,30 @@ var createRoomBundle = (client, options) => {
138
176
  };
139
177
  const useCanRedo = () => {
140
178
  const room = useRoom();
141
- const subscribe = (0, import_react2.useCallback)((onStoreChange) => room.storageRoot(onStoreChange), [room]);
142
- const getSnapshot = (0, import_react2.useCallback)(() => room.canRedo(), [room]);
143
- const canRedo = (0, import_with_selector.useSyncExternalStoreWithSelector)(subscribe, getSnapshot, getSnapshot, identity);
179
+ const subscribe = (0, import_react4.useCallback)((onStoreChange) => room.storageRoot(onStoreChange), [room]);
180
+ const getSnapshot = (0, import_react4.useCallback)(() => room.canRedo(), [room]);
181
+ const canRedo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
144
182
  return canRedo;
145
183
  };
146
184
  const useCanUndo = () => {
147
185
  const room = useRoom();
148
- const subscribe = (0, import_react2.useCallback)((onStoreChange) => room.storageRoot(onStoreChange), [room]);
149
- const getSnapshot = (0, import_react2.useCallback)(() => room.canUndo(), [room]);
150
- const canRedo = (0, import_with_selector.useSyncExternalStoreWithSelector)(subscribe, getSnapshot, getSnapshot, identity);
151
- return canRedo;
186
+ const subscribe = (0, import_react4.useCallback)((onStoreChange) => room.storageRoot(onStoreChange), [room]);
187
+ const getSnapshot = (0, import_react4.useCallback)(() => room.canUndo(), [room]);
188
+ const canUndo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
189
+ return canUndo;
152
190
  };
153
191
  const useConnection = (selector = identity, options2) => {
154
192
  var _a2;
155
193
  const room = useRoom();
156
- const subscribe = (0, import_react2.useCallback)(
194
+ const subscribe = (0, import_react4.useCallback)(
157
195
  (onStoreChange) => {
158
196
  return room.subscribe("connection", onStoreChange);
159
197
  },
160
198
  [room]
161
199
  );
162
200
  const getSnapshot = room.getConnection;
163
- const _selector = (0, import_react2.useCallback)((snapshot) => selector(snapshot), [selector]);
164
- return (0, import_with_selector.useSyncExternalStoreWithSelector)(
201
+ const _selector = (0, import_react4.useCallback)((snapshot) => selector(snapshot), [selector]);
202
+ return useSyncExternalStoreWithSelector(
165
203
  subscribe,
166
204
  getSnapshot,
167
205
  getSnapshot,
@@ -175,7 +213,7 @@ var createRoomBundle = (client, options) => {
175
213
  };
176
214
  const useEvent = (type, callback) => {
177
215
  const room = useRoom();
178
- (0, import_react2.useEffect)(() => {
216
+ (0, import_react4.useEffect)(() => {
179
217
  const unsubscribe = room.event(type, callback);
180
218
  return () => {
181
219
  unsubscribe();
@@ -196,47 +234,40 @@ var createRoomBundle = (client, options) => {
196
234
  const useMyPresence = (selector = identity, options2) => {
197
235
  var _a2;
198
236
  const room = useRoom();
199
- const subscribe = (0, import_react2.useCallback)(
237
+ const subscribe = (0, import_react4.useCallback)(
200
238
  (onStoreChange) => {
201
239
  return room.subscribe("my-presence", onStoreChange);
202
240
  },
203
241
  [room]
204
242
  );
205
243
  const getSnapshot = room.getMyPresence;
206
- const _selector = (0, import_react2.useCallback)((snapshot) => selector(snapshot), [selector]);
207
- const myPresence = (0, import_with_selector.useSyncExternalStoreWithSelector)(
244
+ const _selector = (0, import_react4.useCallback)((snapshot) => selector(snapshot), [selector]);
245
+ const myPresence = useSyncExternalStoreWithSelector(
208
246
  subscribe,
209
247
  getSnapshot,
210
248
  getSnapshot,
211
249
  _selector,
212
250
  (_a2 = options2 == null ? void 0 : options2.isEqual) != null ? _a2 : import_fast_deep_equal.default
213
251
  );
214
- const updateMyPresence = (0, import_react2.useCallback)(
215
- (value) => {
216
- const presence = typeof value === "function" ? value(room.getMyPresence()) : value;
217
- room.updateMyPresence(presence);
218
- },
219
- [room]
220
- );
221
- return [myPresence, updateMyPresence];
252
+ return [myPresence, room.updateMyPresence];
222
253
  };
223
254
  const useMyself = (selector = identity, options2) => {
224
255
  var _a2;
225
256
  const room = useRoom();
226
- const subscribe = (0, import_react2.useCallback)(
257
+ const subscribe = (0, import_react4.useCallback)(
227
258
  (onStoreChange) => {
228
259
  return room.subscribe("myself", onStoreChange);
229
260
  },
230
261
  [room]
231
262
  );
232
263
  const getSnapshot = room.getMyself;
233
- const _selector = (0, import_react2.useCallback)(
264
+ const _selector = (0, import_react4.useCallback)(
234
265
  (snapshot) => {
235
266
  return !snapshot ? null : selector(snapshot);
236
267
  },
237
268
  [selector]
238
269
  );
239
- return (0, import_with_selector.useSyncExternalStoreWithSelector)(
270
+ return useSyncExternalStoreWithSelector(
240
271
  subscribe,
241
272
  getSnapshot,
242
273
  getSnapshot,
@@ -247,20 +278,20 @@ var createRoomBundle = (client, options) => {
247
278
  const useOther = (connectionId, selector = identity, options2) => {
248
279
  var _a2;
249
280
  const room = useRoom();
250
- const subscribe = (0, import_react2.useCallback)(
281
+ const subscribe = (0, import_react4.useCallback)(
251
282
  (onStoreChange) => {
252
283
  return room.other(connectionId, onStoreChange);
253
284
  },
254
285
  [room, connectionId]
255
286
  );
256
- const getSnapshot = (0, import_react2.useCallback)(() => room.getOther(connectionId), [room, connectionId]);
257
- const _selector = (0, import_react2.useCallback)(
287
+ const getSnapshot = (0, import_react4.useCallback)(() => room.getOther(connectionId), [room, connectionId]);
288
+ const _selector = (0, import_react4.useCallback)(
258
289
  (snapshot) => {
259
290
  return !snapshot ? null : selector(snapshot);
260
291
  },
261
292
  [selector]
262
293
  );
263
- return (0, import_with_selector.useSyncExternalStoreWithSelector)(
294
+ return useSyncExternalStoreWithSelector(
264
295
  subscribe,
265
296
  getSnapshot,
266
297
  getSnapshot,
@@ -271,19 +302,24 @@ var createRoomBundle = (client, options) => {
271
302
  const useOthers = (selector = identity, options2) => {
272
303
  var _a2;
273
304
  const room = useRoom();
274
- const subscribe = (0, import_react2.useCallback)(
305
+ const subscribe = (0, import_react4.useCallback)(
275
306
  (onStoreChange) => {
276
307
  return room.subscribe("others", onStoreChange);
277
308
  },
278
309
  [room]
279
310
  );
280
311
  const getSnapshot = room.getOthers;
281
- return (0, import_with_selector.useSyncExternalStoreWithSelector)(
312
+ return useSyncExternalStoreWithSelector(
282
313
  subscribe,
283
314
  getSnapshot,
284
315
  getSnapshot,
285
316
  selector,
286
- (_a2 = options2 == null ? void 0 : options2.isEqual) != null ? _a2 : shallowArrayEqual
317
+ (_a2 = options2 == null ? void 0 : options2.isEqual) != null ? _a2 : (a, b) => {
318
+ if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
319
+ return shallowArrayEqual(a, b);
320
+ }
321
+ return (0, import_fast_deep_equal.default)(a, b);
322
+ }
287
323
  );
288
324
  };
289
325
  const useRedo = () => {
@@ -297,18 +333,18 @@ var createRoomBundle = (client, options) => {
297
333
  room.subscribe("storage-loaded", () => {
298
334
  rerender();
299
335
  });
300
- const subscribe = (0, import_react2.useCallback)((onStoreChange) => room.storage(key, onStoreChange), [key, room]);
301
- const getSnapshot = (0, import_react2.useCallback)(() => {
336
+ const subscribe = (0, import_react4.useCallback)((onStoreChange) => room.storage(key, onStoreChange), [key, room]);
337
+ const getSnapshot = (0, import_react4.useCallback)(() => {
302
338
  var _a3;
303
339
  return (_a3 = room.getStorage(key)) == null ? void 0 : _a3.toJson();
304
340
  }, [key, room]);
305
- const _selector = (0, import_react2.useCallback)(
341
+ const _selector = (0, import_react4.useCallback)(
306
342
  (snapshot) => {
307
343
  return snapshot === null ? null : selector(snapshot);
308
344
  },
309
345
  [selector]
310
346
  );
311
- const data = (0, import_with_selector.useSyncExternalStoreWithSelector)(
347
+ const data = useSyncExternalStoreWithSelector(
312
348
  subscribe,
313
349
  getSnapshot,
314
350
  getSnapshot,
@@ -354,13 +390,13 @@ var createRoomBundle = (client, options) => {
354
390
  // src/createBundle.tsx
355
391
  var import_jsx_runtime2 = require("react/jsx-runtime");
356
392
  var createBundle = (client) => {
357
- const PluvContext = (0, import_react3.createContext)(null);
358
- const PluvProvider = (0, import_react3.memo)((props) => {
393
+ const PluvContext = (0, import_react5.createContext)(null);
394
+ const PluvProvider = (0, import_react5.memo)((props) => {
359
395
  const { children } = props;
360
396
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PluvContext.Provider, { value: client, children });
361
397
  });
362
398
  PluvProvider.displayName = "PluvProvider";
363
- const useClient = () => (0, import_react3.useContext)(PluvContext);
399
+ const useClient = () => (0, import_react5.useContext)(PluvContext);
364
400
  const createRoomBundle2 = (options) => createRoomBundle(client, options);
365
401
  return {
366
402
  createRoomBundle: createRoomBundle2,
package/dist/index.mjs CHANGED
@@ -8,19 +8,33 @@ import { createContext as createContext2, memo as memo2, useContext as useContex
8
8
  import { MockedRoom } from "@pluv/client";
9
9
  import { noop } from "@pluv/crdt";
10
10
  import fastDeepEqual from "fast-deep-equal";
11
- import { createContext, memo, useCallback as useCallback2, useContext, useEffect, useMemo, useState as useState2 } from "react";
11
+ import { createContext, memo, useCallback as useCallback3, useContext, useEffect as useEffect2, useMemo, useState as useState3 } from "react";
12
+
13
+ // src/internal/hooks/useNoSsr.ts
14
+ import { useCallback, useEffect, useState } from "react";
15
+ var useNoSsr = () => {
16
+ const [didMount, setDidMount] = useState(false);
17
+ useEffect(() => setDidMount(typeof window !== "undefined"), []);
18
+ const noSsr = useCallback(
19
+ (value, fallback = null) => {
20
+ return didMount ? typeof value !== "function" ? value : value() : fallback;
21
+ },
22
+ [didMount]
23
+ );
24
+ return noSsr;
25
+ };
12
26
 
13
27
  // src/internal/hooks/useRerender.ts
14
- import { useCallback, useState } from "react";
28
+ import { useCallback as useCallback2, useState as useState2 } from "react";
15
29
  var useRerender = () => {
16
- const [, setCount] = useState(0);
17
- return useCallback(() => {
30
+ const [, setCount] = useState2(0);
31
+ return useCallback2(() => {
18
32
  setCount((oldCount) => (oldCount + 1) % Number.MAX_SAFE_INTEGER);
19
33
  }, []);
20
34
  };
21
35
 
22
36
  // src/internal/hooks/useSyncExternalStoreWithSelector.ts
23
- import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector.js";
37
+ import { useRef, useSyncExternalStore } from "react";
24
38
 
25
39
  // src/internal/utils/identity.ts
26
40
  var identity = (x) => x;
@@ -30,6 +44,30 @@ var shallowArrayEqual = (a, b) => {
30
44
  return a.length === b.length && a.every((item, i) => item === b[i]);
31
45
  };
32
46
 
47
+ // src/internal/utils/shallowEqual.ts
48
+ var shallowEqual = (a, b) => a === b;
49
+
50
+ // src/internal/hooks/useSyncExternalStoreWithSelector.ts
51
+ var useSyncExternalStoreWithSelector = (subscribe, getClientSnapshot, getServerSnapshot, selector, isEqual) => {
52
+ const noSsr = useNoSsr();
53
+ const cacheRef = useRef(null);
54
+ const _getServerSnapshot = getServerSnapshot ? () => {
55
+ const data = getServerSnapshot();
56
+ return selector(data);
57
+ } : void 0;
58
+ const _getClientSnapshot = () => {
59
+ const data = getClientSnapshot();
60
+ const snapshot = selector(data);
61
+ const compare = isEqual != null ? isEqual : shallowEqual;
62
+ const cached = cacheRef.current;
63
+ const isUnchanged = cached === null ? snapshot === null : compare(cached, snapshot);
64
+ cacheRef.current = isUnchanged ? cached != null ? cached : snapshot : snapshot;
65
+ return cacheRef.current;
66
+ };
67
+ const result = useSyncExternalStore(subscribe, _getClientSnapshot, _getServerSnapshot);
68
+ return typeof window !== "undefined" ? noSsr(result, _getServerSnapshot == null ? void 0 : _getServerSnapshot()) : result;
69
+ };
70
+
33
71
  // src/createRoomBundle.tsx
34
72
  import { jsx } from "react/jsx-runtime";
35
73
  var createRoomBundle = (client, options) => {
@@ -39,7 +77,7 @@ var createRoomBundle = (client, options) => {
39
77
  const MockedRoomContext = createContext({ room: null });
40
78
  const MockedRoomProvider = memo((props) => {
41
79
  const { children, events, initialPresence, initialStorage, room: _room } = props;
42
- const [room] = useState2(() => {
80
+ const [room] = useState3(() => {
43
81
  return new MockedRoom(_room, {
44
82
  events,
45
83
  initialPresence,
@@ -54,7 +92,7 @@ var createRoomBundle = (client, options) => {
54
92
  const { children, debug, initialPresence, initialStorage, onAuthorizationFail, room: _room } = props;
55
93
  const rerender = useRerender();
56
94
  const { room: mockedRoom } = useContext(MockedRoomContext);
57
- const [room] = useState2(() => {
95
+ const [room] = useState3(() => {
58
96
  return client.createRoom(_room, {
59
97
  addons: options.addons,
60
98
  debug,
@@ -64,7 +102,7 @@ var createRoomBundle = (client, options) => {
64
102
  onAuthorizationFail
65
103
  });
66
104
  });
67
- useEffect(() => {
105
+ useEffect2(() => {
68
106
  const unsubscribe = room.subscribe("connection", () => {
69
107
  rerender();
70
108
  });
@@ -72,7 +110,7 @@ var createRoomBundle = (client, options) => {
72
110
  unsubscribe();
73
111
  };
74
112
  }, [rerender, room]);
75
- useEffect(() => {
113
+ useEffect2(() => {
76
114
  client.enter(room);
77
115
  return () => {
78
116
  client.leave(room);
@@ -84,7 +122,7 @@ var createRoomBundle = (client, options) => {
84
122
  const useRoom = () => useContext(PluvRoomContext);
85
123
  const useBroadcast = () => {
86
124
  const room = useRoom();
87
- const broadcast = useCallback2(
125
+ const broadcast = useCallback3(
88
126
  (event2, data) => {
89
127
  room.broadcast(event2, data);
90
128
  },
@@ -102,29 +140,29 @@ var createRoomBundle = (client, options) => {
102
140
  };
103
141
  const useCanRedo = () => {
104
142
  const room = useRoom();
105
- const subscribe = useCallback2((onStoreChange) => room.storageRoot(onStoreChange), [room]);
106
- const getSnapshot = useCallback2(() => room.canRedo(), [room]);
143
+ const subscribe = useCallback3((onStoreChange) => room.storageRoot(onStoreChange), [room]);
144
+ const getSnapshot = useCallback3(() => room.canRedo(), [room]);
107
145
  const canRedo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
108
146
  return canRedo;
109
147
  };
110
148
  const useCanUndo = () => {
111
149
  const room = useRoom();
112
- const subscribe = useCallback2((onStoreChange) => room.storageRoot(onStoreChange), [room]);
113
- const getSnapshot = useCallback2(() => room.canUndo(), [room]);
114
- const canRedo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
115
- return canRedo;
150
+ const subscribe = useCallback3((onStoreChange) => room.storageRoot(onStoreChange), [room]);
151
+ const getSnapshot = useCallback3(() => room.canUndo(), [room]);
152
+ const canUndo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
153
+ return canUndo;
116
154
  };
117
155
  const useConnection = (selector = identity, options2) => {
118
156
  var _a2;
119
157
  const room = useRoom();
120
- const subscribe = useCallback2(
158
+ const subscribe = useCallback3(
121
159
  (onStoreChange) => {
122
160
  return room.subscribe("connection", onStoreChange);
123
161
  },
124
162
  [room]
125
163
  );
126
164
  const getSnapshot = room.getConnection;
127
- const _selector = useCallback2((snapshot) => selector(snapshot), [selector]);
165
+ const _selector = useCallback3((snapshot) => selector(snapshot), [selector]);
128
166
  return useSyncExternalStoreWithSelector(
129
167
  subscribe,
130
168
  getSnapshot,
@@ -139,7 +177,7 @@ var createRoomBundle = (client, options) => {
139
177
  };
140
178
  const useEvent = (type, callback) => {
141
179
  const room = useRoom();
142
- useEffect(() => {
180
+ useEffect2(() => {
143
181
  const unsubscribe = room.event(type, callback);
144
182
  return () => {
145
183
  unsubscribe();
@@ -160,14 +198,14 @@ var createRoomBundle = (client, options) => {
160
198
  const useMyPresence = (selector = identity, options2) => {
161
199
  var _a2;
162
200
  const room = useRoom();
163
- const subscribe = useCallback2(
201
+ const subscribe = useCallback3(
164
202
  (onStoreChange) => {
165
203
  return room.subscribe("my-presence", onStoreChange);
166
204
  },
167
205
  [room]
168
206
  );
169
207
  const getSnapshot = room.getMyPresence;
170
- const _selector = useCallback2((snapshot) => selector(snapshot), [selector]);
208
+ const _selector = useCallback3((snapshot) => selector(snapshot), [selector]);
171
209
  const myPresence = useSyncExternalStoreWithSelector(
172
210
  subscribe,
173
211
  getSnapshot,
@@ -175,26 +213,19 @@ var createRoomBundle = (client, options) => {
175
213
  _selector,
176
214
  (_a2 = options2 == null ? void 0 : options2.isEqual) != null ? _a2 : fastDeepEqual
177
215
  );
178
- const updateMyPresence = useCallback2(
179
- (value) => {
180
- const presence = typeof value === "function" ? value(room.getMyPresence()) : value;
181
- room.updateMyPresence(presence);
182
- },
183
- [room]
184
- );
185
- return [myPresence, updateMyPresence];
216
+ return [myPresence, room.updateMyPresence];
186
217
  };
187
218
  const useMyself = (selector = identity, options2) => {
188
219
  var _a2;
189
220
  const room = useRoom();
190
- const subscribe = useCallback2(
221
+ const subscribe = useCallback3(
191
222
  (onStoreChange) => {
192
223
  return room.subscribe("myself", onStoreChange);
193
224
  },
194
225
  [room]
195
226
  );
196
227
  const getSnapshot = room.getMyself;
197
- const _selector = useCallback2(
228
+ const _selector = useCallback3(
198
229
  (snapshot) => {
199
230
  return !snapshot ? null : selector(snapshot);
200
231
  },
@@ -211,14 +242,14 @@ var createRoomBundle = (client, options) => {
211
242
  const useOther = (connectionId, selector = identity, options2) => {
212
243
  var _a2;
213
244
  const room = useRoom();
214
- const subscribe = useCallback2(
245
+ const subscribe = useCallback3(
215
246
  (onStoreChange) => {
216
247
  return room.other(connectionId, onStoreChange);
217
248
  },
218
249
  [room, connectionId]
219
250
  );
220
- const getSnapshot = useCallback2(() => room.getOther(connectionId), [room, connectionId]);
221
- const _selector = useCallback2(
251
+ const getSnapshot = useCallback3(() => room.getOther(connectionId), [room, connectionId]);
252
+ const _selector = useCallback3(
222
253
  (snapshot) => {
223
254
  return !snapshot ? null : selector(snapshot);
224
255
  },
@@ -235,7 +266,7 @@ var createRoomBundle = (client, options) => {
235
266
  const useOthers = (selector = identity, options2) => {
236
267
  var _a2;
237
268
  const room = useRoom();
238
- const subscribe = useCallback2(
269
+ const subscribe = useCallback3(
239
270
  (onStoreChange) => {
240
271
  return room.subscribe("others", onStoreChange);
241
272
  },
@@ -247,7 +278,12 @@ var createRoomBundle = (client, options) => {
247
278
  getSnapshot,
248
279
  getSnapshot,
249
280
  selector,
250
- (_a2 = options2 == null ? void 0 : options2.isEqual) != null ? _a2 : shallowArrayEqual
281
+ (_a2 = options2 == null ? void 0 : options2.isEqual) != null ? _a2 : (a, b) => {
282
+ if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
283
+ return shallowArrayEqual(a, b);
284
+ }
285
+ return fastDeepEqual(a, b);
286
+ }
251
287
  );
252
288
  };
253
289
  const useRedo = () => {
@@ -261,12 +297,12 @@ var createRoomBundle = (client, options) => {
261
297
  room.subscribe("storage-loaded", () => {
262
298
  rerender();
263
299
  });
264
- const subscribe = useCallback2((onStoreChange) => room.storage(key, onStoreChange), [key, room]);
265
- const getSnapshot = useCallback2(() => {
300
+ const subscribe = useCallback3((onStoreChange) => room.storage(key, onStoreChange), [key, room]);
301
+ const getSnapshot = useCallback3(() => {
266
302
  var _a3;
267
303
  return (_a3 = room.getStorage(key)) == null ? void 0 : _a3.toJson();
268
304
  }, [key, room]);
269
- const _selector = useCallback2(
305
+ const _selector = useCallback3(
270
306
  (snapshot) => {
271
307
  return snapshot === null ? null : selector(snapshot);
272
308
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/react",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "@pluv/client React.js bindings",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -17,24 +17,23 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "@types/react": "^18.3.3",
21
- "@types/use-sync-external-store": "^0.0.6",
22
20
  "fast-deep-equal": "^3.1.3",
23
- "use-sync-external-store": "^1.2.2",
24
- "@pluv/client": "^0.18.0",
25
- "@pluv/crdt": "^0.18.0",
26
- "@pluv/types": "^0.18.0"
21
+ "@pluv/client": "^0.19.0",
22
+ "@pluv/crdt": "^0.19.0",
23
+ "@pluv/types": "^0.19.0"
27
24
  },
28
25
  "peerDependencies": {
29
- "react": "^17.0.0 || ^18.0.0"
26
+ "@types/react": "^18.0.0",
27
+ "react": "^18.0.0"
30
28
  },
31
29
  "devDependencies": {
30
+ "@types/react": "^18.3.3",
32
31
  "eslint": "^8.57.0",
33
32
  "react": "^18.3.1",
34
- "tsup": "^8.0.2",
33
+ "tsup": "^8.1.0",
35
34
  "typescript": "^5.4.5",
36
- "@pluv/tsconfig": "^0.18.0",
37
- "eslint-config-pluv": "^0.18.0"
35
+ "@pluv/tsconfig": "^0.19.0",
36
+ "eslint-config-pluv": "^0.19.0"
38
37
  },
39
38
  "scripts": {
40
39
  "build": "tsup src/index.ts --format esm,cjs --dts --external react",
@@ -65,7 +65,7 @@ export type BroadcastProxy<TIO extends IOLike> = (<TEvent extends keyof InferIOI
65
65
  event: TEvent,
66
66
  data: Id<InferIOInput<TIO>[TEvent]>,
67
67
  ) => void) & {
68
- [event in keyof InferIOInput<TIO>]: (callback: Id<InferIOInput<TIO>[event]>) => void;
68
+ [event in keyof InferIOInput<TIO>]: (input: Id<InferIOInput<TIO>[event]>) => void;
69
69
  };
70
70
 
71
71
  export type EventProxy<TIO extends IOLike> = {
@@ -110,12 +110,12 @@ export interface CreateRoomBundle<
110
110
  useOther: <T extends unknown = UserInfo<TIO, TPresence>>(
111
111
  connectionId: string,
112
112
  selector?: (other: UserInfo<TIO, TPresence>) => T,
113
- options?: SubscriptionHookOptions<Id<T> | null>,
114
- ) => Id<T> | null;
115
- useOthers: <T extends unknown = UserInfo<TIO, TPresence>>(
116
- selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T[],
117
- options?: SubscriptionHookOptions<readonly Id<T>[]>,
118
- ) => readonly Id<T>[];
113
+ options?: SubscriptionHookOptions<T | null>,
114
+ ) => T | null;
115
+ useOthers: <T extends unknown = readonly UserInfo<TIO, TPresence>[]>(
116
+ selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T,
117
+ options?: SubscriptionHookOptions<T>,
118
+ ) => T;
119
119
  useRedo: () => () => void;
120
120
  useRoom: () => AbstractRoom<TIO, TPresence, TStorage>;
121
121
  useStorage: <TKey extends keyof TStorage, TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>>(
@@ -258,9 +258,9 @@ export const createRoomBundle = <
258
258
 
259
259
  const getSnapshot = useCallback((): boolean => room.canUndo(), [room]);
260
260
 
261
- const canRedo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
261
+ const canUndo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
262
262
 
263
- return canRedo;
263
+ return canUndo;
264
264
  };
265
265
 
266
266
  const useConnection = <T extends unknown = WebSocketConnection>(
@@ -350,16 +350,7 @@ export const createRoomBundle = <
350
350
  options?.isEqual ?? fastDeepEqual,
351
351
  );
352
352
 
353
- const updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>> = useCallback(
354
- (value: UpdateMyPresenceAction<TPresence>): void => {
355
- const presence = typeof value === "function" ? value(room.getMyPresence()) : value;
356
-
357
- room.updateMyPresence(presence);
358
- },
359
- [room],
360
- );
361
-
362
- return [myPresence, updateMyPresence];
353
+ return [myPresence, room.updateMyPresence];
363
354
  };
364
355
 
365
356
  const useMyself = <T extends unknown = UserInfo<TIO, TPresence>>(
@@ -396,8 +387,8 @@ export const createRoomBundle = <
396
387
  const useOther = <T extends unknown = UserInfo<TIO, TPresence>>(
397
388
  connectionId: string,
398
389
  selector = identity as (other: UserInfo<TIO, TPresence>) => T,
399
- options?: SubscriptionHookOptions<Id<T> | null>,
400
- ): Id<T> | null => {
390
+ options?: SubscriptionHookOptions<T | null>,
391
+ ): T | null => {
401
392
  const room = useRoom();
402
393
 
403
394
  const subscribe = useCallback(
@@ -411,7 +402,7 @@ export const createRoomBundle = <
411
402
 
412
403
  const _selector = useCallback(
413
404
  (snapshot: Id<UserInfo<TIO, TPresence>> | null) => {
414
- return !snapshot ? null : (selector(snapshot) as Id<T>);
405
+ return !snapshot ? null : (selector(snapshot) as T);
415
406
  },
416
407
  [selector],
417
408
  );
@@ -425,10 +416,10 @@ export const createRoomBundle = <
425
416
  );
426
417
  };
427
418
 
428
- const useOthers = <T extends unknown = UserInfo<TIO, TPresence>>(
429
- selector = identity as (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T[],
430
- options?: SubscriptionHookOptions<readonly Id<T>[]>,
431
- ): readonly Id<T>[] => {
419
+ const useOthers = <T extends unknown = readonly UserInfo<TIO, TPresence>[]>(
420
+ selector = identity as (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T,
421
+ options?: SubscriptionHookOptions<T>,
422
+ ): T => {
432
423
  const room = useRoom();
433
424
 
434
425
  const subscribe = useCallback(
@@ -444,8 +435,23 @@ export const createRoomBundle = <
444
435
  subscribe,
445
436
  getSnapshot,
446
437
  getSnapshot,
447
- selector as (other: readonly Id<UserInfo<TIO, TPresence>>[]) => Id<T>[],
448
- options?.isEqual ?? shallowArrayEqual,
438
+ selector as (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T,
439
+ options?.isEqual ??
440
+ ((a, b) => {
441
+ /**
442
+ * !HACK
443
+ * @description Assume the return type will always be an array when this occurs
444
+ * and do a shallow comparison instead like how standard React dependency
445
+ * arrays work. This is for performance's sake and may lead to bugs if the
446
+ * selector doesn't always return an array
447
+ * @date June 22, 2024
448
+ */
449
+ if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
450
+ return shallowArrayEqual(a, b);
451
+ }
452
+
453
+ return fastDeepEqual(a, b);
454
+ }),
449
455
  );
450
456
  };
451
457
 
@@ -1,3 +1,3 @@
1
+ export { useNoSsr } from "./useNoSsr";
1
2
  export { useRerender } from "./useRerender";
2
- export { useSyncExternalStore } from "./useSyncExternalStore";
3
3
  export { useSyncExternalStoreWithSelector } from "./useSyncExternalStoreWithSelector";
@@ -0,0 +1,18 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+
3
+ export type NoSsrFunction = <T = null>(value: T | (() => T), fallback?: T) => T;
4
+
5
+ export const useNoSsr = (): NoSsrFunction => {
6
+ const [didMount, setDidMount] = useState<boolean>(false);
7
+
8
+ useEffect(() => setDidMount(typeof window !== "undefined"), []);
9
+
10
+ const noSsr = useCallback(
11
+ <T = null>(value: T | (() => T), fallback: T = null as T): T => {
12
+ return didMount ? (typeof value !== "function" ? value : (value as () => T)()) : fallback;
13
+ },
14
+ [didMount],
15
+ );
16
+
17
+ return noSsr;
18
+ };
@@ -1 +1,44 @@
1
- export { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector.js";
1
+ import { useRef, useSyncExternalStore } from "react";
2
+ import { shallowEqual } from "../utils";
3
+ import { useNoSsr } from "./useNoSsr";
4
+
5
+ export const useSyncExternalStoreWithSelector = <TSnapshot, TSelection>(
6
+ subscribe: (onStoreChange: () => void) => () => void,
7
+ getClientSnapshot: () => TSnapshot,
8
+ getServerSnapshot: undefined | null | (() => TSnapshot),
9
+ selector: (snapshot: TSnapshot) => TSelection,
10
+ isEqual?: (a: TSelection, b: TSelection) => boolean,
11
+ ): TSelection => {
12
+ const noSsr = useNoSsr();
13
+ const cacheRef = useRef<TSelection | null>(null);
14
+
15
+ const _getServerSnapshot = getServerSnapshot
16
+ ? () => {
17
+ const data = getServerSnapshot();
18
+
19
+ return selector(data);
20
+ }
21
+ : undefined;
22
+
23
+ const _getClientSnapshot = () => {
24
+ const data = getClientSnapshot();
25
+ const snapshot = selector(data);
26
+ const compare = isEqual ?? shallowEqual;
27
+ const cached = cacheRef.current;
28
+
29
+ const isUnchanged = cached === null ? snapshot === null : compare(cached, snapshot);
30
+
31
+ /**
32
+ * !HACK
33
+ * @description Memoize only the last snapshot so that we can only rerender on changes
34
+ * @date June 22, 2024
35
+ */
36
+ cacheRef.current = isUnchanged ? cached ?? snapshot : snapshot;
37
+
38
+ return cacheRef.current;
39
+ };
40
+
41
+ const result = useSyncExternalStore(subscribe, _getClientSnapshot, _getServerSnapshot);
42
+
43
+ return typeof window !== "undefined" ? noSsr(result, _getServerSnapshot?.()) : result;
44
+ };
@@ -1,2 +1,3 @@
1
1
  export { identity } from "./identity";
2
2
  export { shallowArrayEqual } from "./shallowArrayEqual";
3
+ export { shallowEqual } from "./shallowEqual";
@@ -0,0 +1 @@
1
+ export const shallowEqual = (a: any, b: any): boolean => a === b;
package/tsconfig.json CHANGED
@@ -1,5 +1,8 @@
1
1
  {
2
2
  "extends": "@pluv/tsconfig/react-library.json",
3
3
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
4
- "exclude": ["node_modules"]
4
+ "exclude": ["node_modules"],
5
+ "compilerOptions": {
6
+ "lib": ["dom", "esnext"]
7
+ }
5
8
  }
@@ -1,10 +0,0 @@
1
- import { useSyncExternalStoreWithSelector } from "./useSyncExternalStoreWithSelector";
2
- import { identity } from "../utils";
3
-
4
- export const useSyncExternalStore = <TSnapshot extends unknown>(
5
- subscribe: (onStoreChange: () => void) => () => void,
6
- getSnapshot: () => TSnapshot,
7
- getServerSnapshot: undefined | null | (() => TSnapshot),
8
- ) => {
9
- return useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnapshot, identity);
10
- };