UNPKG

@not-true/devtools

Version:

Remote debugging and development tools client library for React Native applications

160 lines (135 loc) 4.86 kB
import { useEffect, useRef, useCallback, useState } from "react"; import { RemoteDevTools } from "./RemoteDevTools"; import { RemoteDevToolsConfig, DevToolsEventHandlers, StateUpdate } from "../types"; import { StateUtils } from "../utils/StateUtils"; /** * Hook to use RemoteDevTools in React components */ export function useRemoteDevTools(config: RemoteDevToolsConfig, handlers: DevToolsEventHandlers = {}) { const devToolsRef = useRef<RemoteDevTools | null>(null); const [isConnected, setIsConnected] = useState(false); const [connectionStatus, setConnectionStatus] = useState<"disconnected" | "connecting" | "connected" | "error">("disconnected"); // Initialize RemoteDevTools useEffect(() => { const enhancedHandlers: DevToolsEventHandlers = { ...handlers, onConnect: () => { setIsConnected(true); setConnectionStatus("connected"); handlers.onConnect?.(); }, onDisconnect: () => { setIsConnected(false); setConnectionStatus("disconnected"); handlers.onDisconnect?.(); }, onReconnect: (attemptNumber) => { setConnectionStatus("connecting"); handlers.onReconnect?.(attemptNumber); }, onReconnectError: (error) => { setConnectionStatus("error"); handlers.onReconnectError?.(error); }, onError: (error) => { setConnectionStatus("error"); handlers.onError?.(error); }, }; devToolsRef.current = new RemoteDevTools(config, enhancedHandlers); return () => { devToolsRef.current?.destroy(); }; }, []); // Connect function const connect = useCallback(async () => { if (!devToolsRef.current) return; try { setConnectionStatus("connecting"); await devToolsRef.current.connect(); } catch (error) { setConnectionStatus("error"); throw error; } }, []); // Disconnect function const disconnect = useCallback(() => { devToolsRef.current?.disconnect(); }, []); // Send state update const updateState = useCallback((stateUpdate: StateUpdate) => { devToolsRef.current?.updateState(stateUpdate); }, []); return { devTools: devToolsRef.current, isConnected, connectionStatus, connect, disconnect, updateState, }; } /** * Hook to automatically sync Redux state */ export function useReduxSync( devTools: RemoteDevTools | null, store: any, // Redux store throttleMs = 500 ) { const lastStateRef = useRef<any>(null); const throttledUpdaterRef = useRef<((stateUpdate: StateUpdate) => void) | null>(null); useEffect(() => { if (!devTools || !store) return; // Create throttled updater throttledUpdaterRef.current = StateUtils.createThrottledUpdater((stateUpdate) => devTools.updateState(stateUpdate), throttleMs); // Subscribe to store changes const unsubscribe = store.subscribe(() => { const currentState = store.getState(); if (throttledUpdaterRef.current) { const stateUpdate = StateUtils.createReduxStateUpdate(StateUtils.sanitizeStateData(currentState)); throttledUpdaterRef.current(stateUpdate); } lastStateRef.current = currentState; }); // Send initial state const initialState = store.getState(); if (initialState && throttledUpdaterRef.current) { const stateUpdate = StateUtils.createReduxStateUpdate(StateUtils.sanitizeStateData(initialState)); throttledUpdaterRef.current(stateUpdate); } return unsubscribe; }, [devTools, store, throttleMs]); } /** * Hook to handle REPL commands with safe evaluation * Client must provide their own command evaluator during RemoteDevTools initialization */ export function useREPLHandler(devTools: RemoteDevTools | null, customEvaluator?: (command: string) => Promise<any>) { useEffect(() => { if (!devTools) return; // REPL handler should be provided during RemoteDevTools initialization // via the onREPLCommand handler in the constructor // This hook is mainly for documentation and future extensibility }, [devTools, customEvaluator]); } /** * Hook to automatically capture and send state from React Context */ export function useContextSync<T>(devTools: RemoteDevTools | null, contextValue: T, contextName: string, throttleMs = 500) { const lastValueRef = useRef<T>(contextValue); const throttledUpdaterRef = useRef<((stateUpdate: StateUpdate) => void) | null>(null); useEffect(() => { if (!devTools) return; throttledUpdaterRef.current = StateUtils.createThrottledUpdater((stateUpdate) => devTools.updateState(stateUpdate), throttleMs); }, [devTools, throttleMs]); useEffect(() => { if (!devTools || !throttledUpdaterRef.current) return; // Only send update if value changed if (contextValue !== lastValueRef.current) { const stateUpdate = StateUtils.createContextStateUpdate(StateUtils.sanitizeStateData(contextValue), contextName); throttledUpdaterRef.current(stateUpdate); lastValueRef.current = contextValue; } }, [devTools, contextValue, contextName]); }