@not-true/devtools
Version:
Remote debugging and development tools client library for React Native applications
160 lines (135 loc) • 4.86 kB
text/typescript
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]);
}