UNPKG

rwsdk

Version:

Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime

65 lines (64 loc) 2.78 kB
import React from "react"; import { getSyncedStateClient } from "./client-core.js"; import { DEFAULT_SYNCED_STATE_PATH } from "./constants.mjs"; const defaultDeps = { useState: React.useState, useEffect: React.useEffect, useRef: React.useRef, useCallback: React.useCallback, }; /** * Builds a `useSyncedState` hook configured with optional endpoint and hook overrides. * @param options Optional overrides for endpoint and React primitives. * @returns Hook that syncs state through the sync state service. */ export const createSyncedStateHook = (options = {}) => { const basePath = options.url ?? DEFAULT_SYNCED_STATE_PATH; const deps = options.hooks ?? defaultDeps; const { useState, useEffect, useRef, useCallback } = deps; return function useSyncedState(initialValue, key, roomId = options.roomId) { if (typeof window === "undefined" && !options.hooks) { return [initialValue, () => { }]; } const resolvedUrl = roomId ? `${basePath}/${roomId}` : basePath; const client = getSyncedStateClient(resolvedUrl); const [value, setValue] = useState(initialValue); const valueRef = useRef(value); valueRef.current = value; const setSyncValue = useCallback((nextValue) => { const resolved = typeof nextValue === "function" ? nextValue(valueRef.current) : nextValue; setValue(resolved); valueRef.current = resolved; void client.setState(resolved, key); }, [client, key, setValue, valueRef]); useEffect(() => { let isActive = true; const handleUpdate = (next) => { if (isActive) { setValue(next); valueRef.current = next; } }; void client.getState(key).then((existing) => { if (existing !== undefined && isActive) { setValue(existing); valueRef.current = existing; } }); void client.subscribe(key, handleUpdate); return () => { isActive = false; // Call unsubscribe when component unmounts // Page reloads are handled by the beforeunload event listener in client-core.ts void client.unsubscribe(key, handleUpdate).catch((error) => { // Log but don't throw - cleanup should not prevent unmounting console.error("[useSyncedState] Error during unsubscribe:", error); }); }; }, [client, key, setValue, valueRef]); return [value, setSyncValue]; }; }; export const useSyncedState = createSyncedStateHook();