import { Option } from 'effect' import { type ReactNode, useEffect, useRef, useSyncExternalStore } from 'react' import { type ClientBinding, connect, type InvokeMessage, type RemoteTransport, type ServerMessage, } from './transport' import type { RemoteContract, RemoteViewSet } from './client' export const useRemoteUI = ( transport: RemoteTransport, views: RemoteViewSet, ): ReactNode => { // A ref, not `useState`: StrictMode double-invokes a state initializer, and the // binding React discards would keep its transport subscription for good. const held = useRef>(Option.none()) const binding = Option.getOrElse(held.current, () => connect({ transport, views })) held.current = Option.some(binding) useEffect(() => () => binding.dispose(), [binding]) useSyncExternalStore(binding.subscribe, binding.snapshot, binding.snapshot) return binding.node() } export interface RemoteUIProps { readonly transport: RemoteTransport readonly views: RemoteViewSet } export const RemoteUI = ({ transport, views, }: RemoteUIProps): ReactNode => useRemoteUI(transport, views) export interface StatusReporter { readonly status: () => S readonly onStatusChange: (listener: () => void) => () => void } export const useConnectionStatus = (reporter: StatusReporter): S => useSyncExternalStore(reporter.onStatusChange, reporter.status, reporter.status)