@zerothrow/react
Version:
React hooks for type-safe error handling with Result types. Stop throwing, start returning.
1 lines • 24.2 kB
Source Map (JSON)
{"version":3,"sources":["../src/hooks/useResult.ts","../src/hooks/useResilientResult.ts","../src/hooks/useResultContext.ts","../src/components/ResultBoundary.tsx"],"names":["useReducer","useRef","useCallback","ZT","useEffect","reducer","state","useContext","createContext","Component"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useCallback, useEffect, useReducer, useRef } from 'react'\nimport { ZT } from '@zerothrow/core'\nimport type { Result } from '@zerothrow/core'\n\nexport interface UseResultOptions {\n /**\n * Whether to execute the function immediately on mount\n * @default true\n */\n immediate?: boolean\n \n /**\n * Dependencies array for re-execution\n */\n deps?: React.DependencyList\n}\n\nexport interface UseResultReturn<T, E extends Error> {\n /**\n * The current Result value (undefined while loading)\n */\n result: Result<T, E> | undefined\n \n /**\n * Whether the async operation is in progress\n */\n loading: boolean\n \n /**\n * Manually trigger execution\n */\n reload: () => void\n \n /**\n * Reset to initial state\n */\n reset: () => void\n}\n\ninterface State<T, E extends Error> {\n result: Result<T, E> | undefined\n loading: boolean\n}\n\ntype Action<T, E extends Error> = \n | { type: 'LOADING' }\n | { type: 'SUCCESS'; result: Result<T, E> }\n | { type: 'RESET' }\n\nfunction reducer<T, E extends Error>(state: State<T, E>, action: Action<T, E>): State<T, E> {\n switch (action.type) {\n case 'LOADING':\n return { ...state, loading: true }\n case 'SUCCESS':\n return { result: action.result, loading: false }\n case 'RESET':\n return { result: undefined, loading: false }\n default:\n return state\n }\n}\n\n/**\n * React hook for handling async operations that return Results.\n * \n * @example\n * ```tsx\n * const { result, loading, reload } = useResult(\n * async () => {\n * const response = await fetch('/api/user')\n * if (!response.ok) return ZT.err(new Error('Failed to fetch'))\n * const data = await response.json()\n * return ZT.ok(data)\n * },\n * { deps: [userId] }\n * )\n * \n * if (loading) return <Spinner />\n * \n * return result?.match({\n * ok: user => <UserProfile {...user} />,\n * err: error => <ErrorMessage error={error} />\n * }) ?? null\n * ```\n */\nexport function useResult<T, E extends Error = Error>(\n fn: () => Promise<Result<T, E>> | Result<T, E>,\n options: UseResultOptions = {}\n): UseResultReturn<T, E> {\n const { immediate = true, deps = [] } = options\n \n const [state, dispatch] = useReducer(reducer<T, E>, {\n result: undefined,\n loading: immediate,\n })\n \n const isMountedRef = useRef(true)\n const abortControllerRef = useRef<AbortController>()\n \n const execute = useCallback(async () => {\n // Cancel any in-flight request\n abortControllerRef.current?.abort()\n abortControllerRef.current = new AbortController()\n \n dispatch({ type: 'LOADING' })\n \n try {\n const result = await fn()\n \n // Only update state if component is still mounted\n if (isMountedRef.current && !abortControllerRef.current.signal.aborted) {\n dispatch({ type: 'SUCCESS', result })\n }\n } catch (error) {\n // If fn throws (which it shouldn't if following Result patterns),\n // convert to Result.err\n if (isMountedRef.current && !abortControllerRef.current.signal.aborted) {\n const errorResult = ZT.err(\n error instanceof Error ? error : new Error(String(error))\n ) as unknown as Result<T, E>\n dispatch({ type: 'SUCCESS', result: errorResult })\n }\n }\n }, deps)\n \n const reset = useCallback(() => {\n abortControllerRef.current?.abort()\n dispatch({ type: 'RESET' })\n }, [])\n \n useEffect(() => {\n if (immediate) {\n execute()\n }\n \n return () => {\n isMountedRef.current = false\n abortControllerRef.current?.abort()\n }\n }, [execute, immediate])\n \n return {\n result: state.result,\n loading: state.loading,\n reload: execute,\n reset,\n }\n}","import { useCallback, useEffect, useReducer, useRef } from 'react'\nimport { ZT } from '@zerothrow/core'\nimport type { Result } from '@zerothrow/core'\nimport type { Policy, RetryPolicyInterface, CircuitBreakerPolicyInterface } from '@zerothrow/resilience'\n\ntype CircuitState = 'closed' | 'open' | 'half-open'\n\nexport interface UseResilientResultOptions {\n /**\n * Whether to execute the function immediately on mount\n * @default true\n */\n immediate?: boolean\n \n /**\n * Dependencies array for re-execution\n */\n deps?: React.DependencyList\n}\n\nexport interface UseResilientResultReturn<T, E extends Error> {\n /**\n * The current Result value (undefined while loading)\n */\n result: Result<T, E> | undefined\n \n /**\n * Whether the async operation is in progress\n */\n loading: boolean\n \n /**\n * Number of retry attempts made\n */\n retryCount: number\n \n /**\n * Timestamp when the next retry will occur (if applicable)\n */\n nextRetryAt: number | undefined\n \n /**\n * Current state of the circuit breaker (if using CircuitBreakerPolicy)\n */\n circuitState: CircuitState | undefined\n \n /**\n * Manually trigger execution\n */\n reload: () => void\n \n /**\n * Reset to initial state\n */\n reset: () => void\n}\n\ninterface State<T, E extends Error> {\n result: Result<T, E> | undefined\n loading: boolean\n retryCount: number\n nextRetryAt: number | undefined\n circuitState: CircuitState | undefined\n}\n\ntype Action<T, E extends Error> = \n | { type: 'LOADING' }\n | { type: 'SUCCESS'; result: Result<T, E> }\n | { type: 'RETRY_SCHEDULED'; nextRetryAt: number; retryCount: number }\n | { type: 'CIRCUIT_STATE_CHANGED'; state: CircuitState }\n | { type: 'RESET' }\n\nfunction reducer<T, E extends Error>(state: State<T, E>, action: Action<T, E>): State<T, E> {\n switch (action.type) {\n case 'LOADING':\n return { ...state, loading: true }\n case 'SUCCESS':\n return { \n ...state, \n result: action.result, \n loading: false,\n nextRetryAt: undefined as number | undefined\n }\n case 'RETRY_SCHEDULED':\n return {\n ...state,\n nextRetryAt: action.nextRetryAt,\n retryCount: action.retryCount\n }\n case 'CIRCUIT_STATE_CHANGED':\n return {\n ...state,\n circuitState: action.state\n }\n case 'RESET':\n return { \n result: undefined, \n loading: false, \n retryCount: 0,\n nextRetryAt: undefined as number | undefined,\n circuitState: undefined as CircuitState | undefined\n }\n default:\n return state\n }\n}\n\n/**\n * React hook for handling async operations with resilience policies.\n * \n * @example\n * ```tsx\n * import { RetryPolicy, CircuitBreakerPolicy } from '@zerothrow/resilience'\n * \n * const policy = RetryPolicy.exponential({ maxRetries: 3 })\n * .chain(CircuitBreakerPolicy.create({ \n * failureThreshold: 5,\n * resetTimeout: 30000 \n * }))\n * \n * const { result, loading, retryCount, nextRetryAt } = useResilientResult(\n * async () => {\n * const response = await fetch('/api/flaky-endpoint')\n * if (!response.ok) throw new Error('Request failed')\n * return response.json()\n * },\n * policy,\n * { deps: [userId] }\n * )\n * \n * if (loading) {\n * return nextRetryAt \n * ? <div>Retrying in {timeUntil(nextRetryAt)}...</div>\n * : <Spinner />\n * }\n * \n * return result?.match({\n * ok: data => <DataView {...data} />,\n * err: error => <ErrorView error={error} retries={retryCount} />\n * }) ?? null\n * ```\n */\nexport function useResilientResult<T, E extends Error = Error>(\n fn: () => Promise<T>,\n policy: Policy,\n options: UseResilientResultOptions = {}\n): UseResilientResultReturn<T, E> {\n const { immediate = true, deps = [] } = options\n \n const [state, dispatch] = useReducer(reducer<T, E>, {\n result: undefined,\n loading: immediate,\n retryCount: 0,\n nextRetryAt: undefined,\n circuitState: undefined\n })\n \n const isMountedRef = useRef(true)\n const abortControllerRef = useRef<AbortController>()\n const retryTimeoutRef = useRef<NodeJS.Timeout>()\n \n const execute = useCallback(async () => {\n // Cancel any in-flight request or scheduled retry\n abortControllerRef.current?.abort()\n abortControllerRef.current = new AbortController()\n if (retryTimeoutRef.current) {\n clearTimeout(retryTimeoutRef.current)\n retryTimeoutRef.current = undefined\n }\n \n dispatch({ type: 'LOADING' })\n \n // If policy has retry capability, track retry metadata\n let wrappedPolicy = policy\n if ('onRetry' in policy) {\n wrappedPolicy = (policy as RetryPolicyInterface).onRetry((attempt: number, _error: unknown, delay: number) => {\n if (isMountedRef.current && !abortControllerRef.current?.signal.aborted) {\n dispatch({ \n type: 'RETRY_SCHEDULED', \n nextRetryAt: Date.now() + delay,\n retryCount: attempt \n })\n }\n })\n }\n \n // If policy has circuit breaker, track its state\n if ('onCircuitStateChange' in wrappedPolicy) {\n (wrappedPolicy as CircuitBreakerPolicyInterface).onCircuitStateChange((state: CircuitState) => {\n if (isMountedRef.current && !abortControllerRef.current?.signal.aborted) {\n dispatch({ type: 'CIRCUIT_STATE_CHANGED', state })\n }\n })\n }\n \n try {\n const result = await wrappedPolicy.execute(fn)\n \n if (isMountedRef.current && !abortControllerRef.current?.signal.aborted) {\n dispatch({ type: 'SUCCESS', result: result as Result<T, E> })\n }\n } catch (error) {\n // This should rarely happen with policies, but handle it gracefully\n if (isMountedRef.current && !abortControllerRef.current?.signal.aborted) {\n const errorResult = ZT.err(\n error instanceof Error ? error : new Error(String(error))\n ) as unknown as Result<T, E>\n dispatch({ type: 'SUCCESS', result: errorResult })\n }\n }\n }, [fn, policy, ...deps])\n \n const reset = useCallback(() => {\n abortControllerRef.current?.abort()\n if (retryTimeoutRef.current) {\n clearTimeout(retryTimeoutRef.current)\n retryTimeoutRef.current = undefined\n }\n dispatch({ type: 'RESET' })\n }, [])\n \n useEffect(() => {\n if (immediate) {\n execute()\n }\n \n return () => {\n isMountedRef.current = false\n abortControllerRef.current?.abort()\n if (retryTimeoutRef.current) {\n clearTimeout(retryTimeoutRef.current)\n }\n }\n }, [execute, immediate])\n \n return {\n result: state.result,\n loading: state.loading,\n retryCount: state.retryCount,\n nextRetryAt: state.nextRetryAt,\n circuitState: state.circuitState,\n reload: execute,\n reset,\n }\n}","import { useContext, createContext, type Context } from 'react'\nimport { ZT, type Result } from '@zerothrow/core'\n\nexport class ContextError extends Error {\n readonly code = 'CONTEXT_NOT_FOUND' as const\n readonly contextName: string\n \n constructor(contextName: string) {\n super(`useResultContext: Context \"${contextName}\" not found. Did you forget to wrap your component in a provider?`)\n this.name = 'ContextError'\n this.contextName = contextName\n }\n}\n\n/**\n * A Result-based version of React's useContext hook.\n * \n * Instead of throwing when context is not available, this hook returns\n * a Result that can be pattern matched for safe error handling.\n * \n * @example\n * ```tsx\n * const ThemeContext = createContext<Theme | undefined>(undefined)\n * \n * function MyComponent() {\n * const themeResult = useResultContext(ThemeContext)\n * \n * return themeResult.match({\n * ok: (theme) => <div style={{ color: theme.primary }}>Themed</div>,\n * err: (error) => <div>No theme provider found</div>\n * })\n * }\n * ```\n */\nexport function useResultContext<T>(\n context: Context<T | undefined>,\n options?: {\n /** Custom context name for better error messages */\n contextName?: string\n }\n): Result<T, Error> {\n const value = useContext(context)\n \n if (value === undefined) {\n const contextName = options?.contextName || context.displayName || 'Unknown'\n return ZT.err(new ContextError(contextName))\n }\n \n return ZT.ok(value)\n}\n\n/**\n * A Result-based version of React's useContext hook that handles null values.\n * \n * This variant treats both undefined and null as missing context values.\n * \n * @example\n * ```tsx\n * const AuthContext = createContext<User | null>(null)\n * \n * function Profile() {\n * const userResult = useResultContextNullable(AuthContext)\n * \n * return userResult.match({\n * ok: (user) => <div>Welcome {user.name}</div>,\n * err: () => <div>Please log in</div>\n * })\n * }\n * ```\n */\nexport function useResultContextNullable<T>(\n context: Context<T | undefined | null>,\n options?: {\n /** Custom context name for better error messages */\n contextName?: string\n }\n): Result<T, Error> {\n const value = useContext(context)\n \n if (value === undefined || value === null) {\n const contextName = options?.contextName || context.displayName || 'Unknown'\n return ZT.err(new ContextError(contextName))\n }\n \n return ZT.ok(value)\n}\n\n/**\n * Creates a Result-based context with a companion hook.\n * \n * This helper creates both a Context and a custom hook that uses\n * useResultContext internally, providing a complete solution for\n * Result-based context patterns.\n * \n * @example\n * ```tsx\n * const { Provider, useContext } = createResultContext<UserSettings>('UserSettings')\n * \n * // In your app\n * <Provider value={settings}>\n * <App />\n * </Provider>\n * \n * // In a component\n * function Profile() {\n * const settingsResult = useContext()\n * \n * return settingsResult.match({\n * ok: (settings) => <div>{settings.name}</div>,\n * err: () => <div>No settings available</div>\n * })\n * }\n * ```\n */\nexport function createResultContext<T>(contextName: string) {\n const Context = createContext<T | undefined>(undefined)\n Context.displayName = contextName\n \n return {\n Provider: Context.Provider,\n useContext: () => useResultContext(Context, { contextName }),\n Context\n }\n}","import { Component } from 'react'\nimport type { ReactNode, ErrorInfo } from 'react'\nimport { ZT } from '@zerothrow/core'\nimport type { Result } from '@zerothrow/core'\n\nexport interface ResultBoundaryProps {\n /**\n * Fallback component to render when an error is caught\n */\n fallback: (result: Result<never, Error>, reset: () => void) => ReactNode\n \n /**\n * Optional error handler for logging/telemetry\n */\n onError?: (error: Error, errorInfo: ErrorInfo) => void\n \n /**\n * Children to render when no error\n */\n children: ReactNode\n}\n\ninterface ResultBoundaryState {\n hasError: boolean\n error: Error | null\n}\n\n/**\n * Error boundary that converts thrown errors to Result types.\n * \n * Unlike standard error boundaries, this provides the error as a Result\n * to the fallback component, enabling type-safe error handling.\n * \n * @example\n * ```tsx\n * <ResultBoundary\n * fallback={(result, reset) => (\n * <ErrorFallback\n * error={result.error}\n * onRetry={reset}\n * />\n * )}\n * onError={(error, info) => {\n * console.error('Boundary caught:', error)\n * sendToTelemetry(error, info)\n * }}\n * >\n * <App />\n * </ResultBoundary>\n * ```\n */\nexport class ResultBoundary extends Component<ResultBoundaryProps, ResultBoundaryState> {\n constructor(props: ResultBoundaryProps) {\n super(props)\n this.state = { hasError: false, error: null }\n }\n \n static getDerivedStateFromError(error: Error): ResultBoundaryState {\n // Update state so the next render will show the fallback UI\n return { hasError: true, error }\n }\n \n override componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n // Log the error to error reporting service\n this.props.onError?.(error, errorInfo)\n }\n \n reset = () => {\n this.setState({ hasError: false, error: null })\n }\n \n override render() {\n if (this.state.hasError && this.state.error) {\n // Convert the error to a Result and pass to fallback\n const result = ZT.err(this.state.error) as Result<never, Error>\n return this.props.fallback(result, this.reset)\n }\n \n return this.props.children\n }\n}"]}