Skip to content

Commit 3b8add4

Browse files
committed
feat: playground for provider-react and renamed hooks.
1 parent 200e822 commit 3b8add4

20 files changed

+523
-49
lines changed

packages/provider-react/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
"yjs": "^13.6.8"
2727
},
2828
"devDependencies": {
29-
"@types/react": "^18.0.0",
30-
"react": "^18.0.0"
29+
"@types/react": "^19.0.0",
30+
"react": "^19.0.0"
3131
},
3232
"repository": {
3333
"url": "https://github.com/ueberdosis/hocuspocus"

packages/provider-react/src/HocuspocusProviderComponent.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import { HocuspocusProviderWebsocket } from "@hocuspocus/provider";
24
import { useEffect, useMemo, useRef } from "react";
35

@@ -25,6 +27,7 @@ export function HocuspocusProviderComponent({
2527
websocketProvider: externalWebsocketProvider,
2628
}: HocuspocusProviderComponentProps) {
2729
const websocketRef = useRef<HocuspocusProviderWebsocket | null>(null);
30+
const destroyTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
2831

2932
// Create WebSocket provider once on mount
3033
if (!websocketRef.current && !externalWebsocketProvider) {
@@ -37,13 +40,22 @@ export function HocuspocusProviderComponent({
3740
externalWebsocketProvider ??
3841
(websocketRef.current as HocuspocusProviderWebsocket);
3942

40-
// Cleanup on unmount
43+
// Cleanup on unmount with deferred destruction to handle StrictMode double-mount
4144
useEffect(() => {
45+
if (destroyTimeoutRef.current) {
46+
clearTimeout(destroyTimeoutRef.current);
47+
destroyTimeoutRef.current = null;
48+
}
49+
4250
return () => {
4351
// Only destroy if we created the websocket (not externally provided)
44-
if (!externalWebsocketProvider && websocketRef.current) {
45-
websocketRef.current.destroy();
46-
websocketRef.current = null;
52+
if (!externalWebsocketProvider) {
53+
destroyTimeoutRef.current = setTimeout(() => {
54+
if (websocketRef.current) {
55+
websocketRef.current.destroy();
56+
websocketRef.current = null;
57+
}
58+
}, 0);
4759
}
4860
};
4961
}, [externalWebsocketProvider]);

packages/provider-react/src/HocuspocusRoom.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import { HocuspocusProvider } from "@hocuspocus/provider";
24
import { useContext, useEffect, useMemo, useRef } from "react";
35

@@ -65,6 +67,9 @@ export function HocuspocusRoom({
6567
destroyTimeoutRef.current = null;
6668
}
6769

70+
// Attach the provider to the websocket so it starts syncing
71+
provider.attach();
72+
6873
return () => {
6974
// Deferred destruction - wait for potential remount in StrictMode
7075
// Using setTimeout(0) allows React to remount before we destroy
@@ -92,6 +97,7 @@ export function HocuspocusRoom({
9297
document: propsRef.current.document,
9398
token: propsRef.current.token,
9499
});
100+
providerRef.current.attach();
95101
}
96102
}, [name, websocketProvider]);
97103

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { useCollabUsers } from "./useCollabUsers.ts";
2-
export { useConnectionStatus } from "./useConnectionStatus.ts";
3-
export { useHocuspocus } from "./useHocuspocus.ts";
4-
export { useSyncStatus } from "./useSyncStatus.ts";
1+
export { useHocuspocusAwareness } from "./useHocuspocusAwareness.ts";
2+
export { useHocuspocusConnectionStatus } from "./useHocuspocusConnectionStatus.ts";
3+
export { useHocuspocusProvider } from "./useHocuspocusProvider.ts";
4+
export { useHocuspocusSyncStatus } from "./useHocuspocusSyncStatus.ts";

packages/provider-react/src/hooks/useCollabUsers.ts renamed to packages/provider-react/src/hooks/useHocuspocusAwareness.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useCallback, useRef, useSyncExternalStore } from "react";
22

33
import type { CollabUser } from "../types.ts";
4-
import { useHocuspocus } from "./useHocuspocus.ts";
4+
import { useHocuspocusProvider } from "./useHocuspocusProvider.ts";
55

66
/**
77
* Subscribe to the list of connected users in the document.
@@ -14,7 +14,7 @@ import { useHocuspocus } from "./useHocuspocus.ts";
1414
* @example
1515
* ```tsx
1616
* function UserList() {
17-
* const users = useCollabUsers()
17+
* const users = useHocuspocusAwareness()
1818
*
1919
* return (
2020
* <div className="avatars">
@@ -32,8 +32,8 @@ import { useHocuspocus } from "./useHocuspocus.ts";
3232
* }
3333
* ```
3434
*/
35-
export function useCollabUsers(): CollabUser[] {
36-
const provider = useHocuspocus();
35+
export function useHocuspocusAwareness(): CollabUser[] {
36+
const provider = useHocuspocusProvider();
3737

3838
// Cache the last snapshot to avoid unnecessary array allocations
3939
const cacheRef = useRef<{

packages/provider-react/src/hooks/useConnectionStatus.ts renamed to packages/provider-react/src/hooks/useHocuspocusConnectionStatus.ts

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useCallback, useRef, useSyncExternalStore } from "react";
22

33
import type { ConnectionStatus } from "../types.ts";
4-
import { useHocuspocus } from "./useHocuspocus.ts";
4+
import { useHocuspocusProvider } from "./useHocuspocusProvider.ts";
55

66
/**
77
* Subscribe to the connection status of the collaboration provider.
@@ -14,7 +14,7 @@ import { useHocuspocus } from "./useHocuspocus.ts";
1414
* @example
1515
* ```tsx
1616
* function ConnectionIndicator() {
17-
* const status = useConnectionStatus()
17+
* const status = useHocuspocusConnectionStatus()
1818
*
1919
* return (
2020
* <div className={`status-${status}`}>
@@ -24,34 +24,31 @@ import { useHocuspocus } from "./useHocuspocus.ts";
2424
* }
2525
* ```
2626
*/
27-
export function useConnectionStatus(): ConnectionStatus {
28-
const provider = useHocuspocus();
29-
// Track connection status via events since the provider doesn't expose a status property
30-
const statusRef = useRef<ConnectionStatus>("connecting");
27+
export function useHocuspocusConnectionStatus(): ConnectionStatus {
28+
const provider = useHocuspocusProvider();
29+
const statusRef = useRef<ConnectionStatus>(
30+
provider.configuration.websocketProvider.status as ConnectionStatus,
31+
);
3132

3233
const subscribe = useCallback(
3334
(onStoreChange: () => void) => {
34-
const handleConnect = () => {
35-
statusRef.current = "connected";
35+
const handleStatus = (data: { status: ConnectionStatus }) => {
36+
statusRef.current = data.status;
3637
onStoreChange();
3738
};
3839

39-
const handleDisconnect = () => {
40-
statusRef.current = "disconnected";
41-
onStoreChange();
42-
};
40+
provider.on("status", handleStatus);
4341

44-
provider.on("connect", handleConnect);
45-
provider.on("disconnect", handleDisconnect);
46-
47-
// Set initial status based on current state
48-
if (provider.isSynced) {
49-
statusRef.current = "connected";
42+
// Sync initial status in case it changed between render and subscribe
43+
const currentStatus = provider.configuration.websocketProvider
44+
.status as ConnectionStatus;
45+
if (statusRef.current !== currentStatus) {
46+
statusRef.current = currentStatus;
47+
onStoreChange();
5048
}
5149

5250
return () => {
53-
provider.off("connect", handleConnect);
54-
provider.off("disconnect", handleDisconnect);
51+
provider.off("status", handleStatus);
5552
};
5653
},
5754
[provider],

packages/provider-react/src/hooks/useHocuspocus.ts renamed to packages/provider-react/src/hooks/useHocuspocusProvider.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { HocuspocusRoomContext } from "../context.ts";
1313
* @example
1414
* ```tsx
1515
* function Editor() {
16-
* const provider = useHocuspocus()
16+
* const provider = useHocuspocusProvider()
1717
*
1818
* const editor = useEditor({
1919
* extensions: [
@@ -26,11 +26,11 @@ import { HocuspocusRoomContext } from "../context.ts";
2626
* }
2727
* ```
2828
*/
29-
export function useHocuspocus() {
29+
export function useHocuspocusProvider() {
3030
const context = useContext(HocuspocusRoomContext);
3131

3232
if (!context) {
33-
throw new Error("useHocuspocus must be used within a HocuspocusRoom");
33+
throw new Error("useHocuspocusProvider must be used within a HocuspocusRoom");
3434
}
3535

3636
return context.provider;

packages/provider-react/src/hooks/useSyncStatus.ts renamed to packages/provider-react/src/hooks/useHocuspocusSyncStatus.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useCallback, useSyncExternalStore } from "react";
22

33
import type { SyncStatus } from "../types.ts";
4-
import { useHocuspocus } from "./useHocuspocus.ts";
4+
import { useHocuspocusProvider } from "./useHocuspocusProvider.ts";
55

66
/**
77
* Subscribe to the sync status of local changes.
@@ -13,7 +13,7 @@ import { useHocuspocus } from "./useHocuspocus.ts";
1313
* @example
1414
* ```tsx
1515
* function SaveIndicator() {
16-
* const syncStatus = useSyncStatus()
16+
* const syncStatus = useHocuspocusSyncStatus()
1717
*
1818
* return (
1919
* <div>
@@ -23,8 +23,8 @@ import { useHocuspocus } from "./useHocuspocus.ts";
2323
* }
2424
* ```
2525
*/
26-
export function useSyncStatus(): SyncStatus {
27-
const provider = useHocuspocus();
26+
export function useHocuspocusSyncStatus(): SyncStatus {
27+
const provider = useHocuspocusProvider();
2828

2929
const subscribe = useCallback(
3030
(onStoreChange: () => void) => {

packages/provider-react/src/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
// Contexts
24
export { HocuspocusContext, HocuspocusRoomContext } from "./context.ts";
35

@@ -7,10 +9,10 @@ export { HocuspocusRoom } from "./HocuspocusRoom.tsx";
79

810
// Hooks
911
export {
10-
useCollabUsers,
11-
useConnectionStatus,
12-
useHocuspocus,
13-
useSyncStatus,
12+
useHocuspocusAwareness,
13+
useHocuspocusConnectionStatus,
14+
useHocuspocusProvider,
15+
useHocuspocusSyncStatus,
1416
} from "./hooks/index.ts";
1517

1618
// Utils

playground/frontend/app/articles/[slug]/CollaborationStatus.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const CollaborationStatus = (props: {
1212

1313
const [unsyncedChanges, setUnsyncedChanges] = useState(0);
1414
const [socketStatus, setSocketStatus] = useState<string | null>(null);
15-
const [isAttached, _setAttached] = useState<boolean | null>(null);
15+
const [isAttached, _setAttached] = useState<boolean>(false);
1616

1717
useEffect(() => {
1818
setSocketStatus(provider.configuration.websocketProvider.status);

0 commit comments

Comments
 (0)