tryError Documentation

React Hooks

Complete guide to using tryError hooks in React applications.

useTry
Async Operation Hook
Handle async operations with loading states and error handling

Basic Usage

useTry basic exampletypescript
import { useTry } from '@try-error/react';

function UserProfile({ userId }: { userId: string }) {
  const { data, error, loading, execute, reset } = useTry(
    async () => {
      const response = await fetch(`/api/users/${userId}`);
      if (!response.ok) throw new Error('Failed to fetch user');
      return response.json();
    },
    [userId] // Dependencies - will re-execute when userId changes
  );

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!data) return <div>No user found</div>;

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.email}</p>
      <button onClick={reset}>Refresh</button>
    </div>
  );
}

API Reference

useTry type signaturetypescript
function useTry<T>(
  operation: () => Promise<T>,
  deps?: React.DependencyList,
  options?: UseTryOptions
): UseTryResult<T>

interface UseTryOptions {
  immediate?: boolean;        // Execute immediately on mount (default: true)
  resetOnDepsChange?: boolean; // Reset state when deps change (default: true)
  retryCount?: number;        // Number of automatic retries (default: 0)
  retryDelay?: number;        // Delay between retries in ms (default: 1000)
}

interface UseTryResult<T> {
  data: T | null;            // Success result
  error: TryError | null;    // Error result
  loading: boolean;          // Loading state
  execute: () => Promise<void>; // Manual execution function
  reset: () => void;         // Reset state function
}

Advanced Configuration

useTry with optionstypescript
function DataFetcher() {
  const { data, error, loading, execute } = useTry(
    async () => {
      const result = await tryAsync(() => fetchCriticalData());
      if (isTryError(result)) {
        throw result; // Re-throw to trigger retry logic
      }
      return result;
    },
    [], // No dependencies - manual execution only
    {
      immediate: false,    // Don't execute on mount
      retryCount: 3,       // Retry up to 3 times
      retryDelay: 2000,    // Wait 2 seconds between retries
    }
  );

  return (
    <div>
      <button onClick={execute} disabled={loading}>
        {loading ? 'Fetching...' : 'Fetch Data'}
      </button>
      {error && <ErrorDisplay error={error} />}
      {data && <DataDisplay data={data} />}
    </div>
  );
}
useTryCallback
Event Handler Hook
Create error-safe event handlers with loading states

Basic Usage

useTryCallback exampletypescript
import { useTryCallback } from '@try-error/react';

function CreateUserForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const { execute: createUser, loading, error } = useTryCallback(
    async (formData: { name: string; email: string }) => {
      const result = await tryAsync(() => 
        fetch('/api/users', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(formData)
        })
      );

      if (isTryError(result)) {
        throw result;
      }

      if (!result.ok) {
        throw createError({
          type: 'ValidationError',
          message: 'Failed to create user',
          context: { status: result.status }
        });
      }

      return result.json();
    },
    {
      onSuccess: (user) => {
        console.log('User created:', user);
        setName('');
        setEmail('');
      },
      onError: (error) => {
        console.error('Failed to create user:', error);
      }
    }
  );

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    createUser({ name, email });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={name} 
        onChange={(e) => setName(e.target.value)}
        placeholder="Name"
        disabled={loading}
      />
      <input 
        value={email} 
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        disabled={loading}
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create User'}
      </button>
      {error && <div className="error">{error.message}</div>}
    </form>
  );
}

API Reference

useTryCallback type signaturetypescript
function useTryCallback<TArgs extends any[], TResult>(
  callback: (...args: TArgs) => Promise<TResult>,
  options?: UseTryCallbackOptions<TResult>
): UseTryCallbackResult<TArgs>

interface UseTryCallbackOptions<T> {
  onSuccess?: (result: T) => void;
  onError?: (error: TryError) => void;
  onSettled?: () => void;
}

interface UseTryCallbackResult<TArgs extends any[]> {
  execute: (...args: TArgs) => Promise<void>;
  loading: boolean;
  error: TryError | null;
  reset: () => void;
}
useTryState
State Management Hook
Manage async state with built-in error handling

Basic Usage

useTryState exampletypescript
import { useTryState } from '@try-error/react';

interface User {
  id: string;
  name: string;
  email: string;
}

function UserManager() {
  const [users, setUsers, { loading, error, reset }] = useTryState<User[]>([]);

  const loadUsers = async () => {
    const result = await tryAsync(() => fetch('/api/users').then(r => r.json()));
    setUsers(result); // Automatically handles success/error states
  };

  const addUser = async (newUser: Omit<User, 'id'>) => {
    const result = await tryAsync(async () => {
      const response = await fetch('/api/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newUser)
      });
      return response.json();
    });

    if (isOk(result)) {
      setUsers(prev => isOk(prev) ? [...prev, result] : [result]);
    }
  };

  useEffect(() => {
    loadUsers();
  }, []);

  if (loading) return <div>Loading users...</div>;
  if (error) return <ErrorDisplay error={error} onRetry={reset} />;
  if (!isOk(users)) return <div>No users</div>;

  return (
    <div>
      <UserList users={users} />
      <AddUserForm onAdd={addUser} />
    </div>
  );
}

API Reference

useTryState type signaturetypescript
function useTryState<T>(
  initialValue: T
): [
  TryResult<T>,
  (value: TryResult<T> | ((prev: TryResult<T>) => TryResult<T>)) => void,
  {
    loading: boolean;
    error: TryError | null;
    reset: () => void;
  }
]
useTryMutation
Mutation Hook
Handle mutations with optimistic updates and rollback

Optimistic Updates

useTryMutation with optimistic updatestypescript
import { useTryMutation } from '@try-error/react';

function TodoList({ todos, setTodos }: { 
  todos: Todo[], 
  setTodos: (todos: Todo[]) => void 
}) {
  const { mutate: toggleTodo, loading } = useTryMutation({
    mutationFn: async (todoId: string) => {
      const result = await tryAsync(() => 
        fetch(`/api/todos/${todoId}/toggle`, { method: 'PUT' })
      );
      
      if (isTryError(result)) throw result;
      return result.json();
    },
    onMutate: async (todoId) => {
      // Optimistic update
      const previousTodos = todos;
      setTodos(todos.map(todo => 
        todo.id === todoId 
          ? { ...todo, completed: !todo.completed }
          : todo
      ));
      return { previousTodos };
    },
    onError: (error, todoId, context) => {
      // Rollback on error
      if (context?.previousTodos) {
        setTodos(context.previousTodos);
      }
    },
    onSuccess: (data, todoId) => {
      // Sync with server response
      setTodos(prev => prev.map(todo => 
        todo.id === todoId ? data : todo
      ));
    }
  });

  return (
    <div>
      {todos.map(todo => (
        <TodoItem 
          key={todo.id}
          todo={todo}
          onToggle={() => toggleTodo(todo.id)}
          disabled={loading}
        />
      ))}
    </div>
  );
}
useCleanup
Memory Management Hook
Universal cleanup hook for proper memory management in React

Basic Usage

useCleanup basic exampletypescript
import { useCleanup } from '@try-error/react';

function DataFetcher() {
  const { isMounted, addCleanup, createAbortController } = useCleanup();
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = createAbortController();

    async function fetchData() {
      try {
        const response = await fetch('/api/data', { 
          signal: controller.signal 
        });
        
        if (isMounted()) {
          setData(await response.json());
        }
      } catch (err) {
        if (isMounted() && !controller.signal.aborted) {
          setError(err);
        }
      }
    }

    fetchData();

    // Register additional cleanup
    addCleanup(() => {
      console.log('Component cleanup executed');
    });
  }, [isMounted, addCleanup, createAbortController]);

  return (
    <div>
      {error && <div>Error: {error.message}</div>}
      {data && <div>Data: {JSON.stringify(data)}</div>}
    </div>
  );
}

Advanced Usage with Ref Management

useCleanup with ref nullificationtypescript
import { useCleanup } from '@try-error/react';

function VideoPlayer({ videoUrl }: { videoUrl: string }) {
  const { isMounted, addCleanup, createAbortController, nullifyRef } = useCleanup();
  const videoRef = useRef<HTMLVideoElement>(null);
  const streamRef = useRef<MediaStream | null>(null);

  useEffect(() => {
    const controller = createAbortController();

    async function setupVideo() {
      try {
        const stream = await navigator.mediaDevices.getUserMedia({ video: true });
        streamRef.current = stream;
        
        if (isMounted() && videoRef.current) {
          videoRef.current.srcObject = stream;
        }

        // Register cleanup for the stream
        addCleanup(() => {
          if (streamRef.current) {
            streamRef.current.getTracks().forEach(track => track.stop());
          }
        });

        // Register ref for nullification
        nullifyRef(streamRef);
        
      } catch (err) {
        if (isMounted()) {
          console.error('Failed to setup video:', err);
        }
      }
    }

    setupVideo();
  }, [videoUrl, isMounted, addCleanup, createAbortController, nullifyRef]);

  return <video ref={videoRef} autoPlay muted />;
}

Custom Hook Integration

useCleanup in custom hookstypescript
import { useCleanup } from '@try-error/react';

function useWebSocket(url: string) {
  const { isMounted, addCleanup, createAbortController } = useCleanup();
  const [socket, setSocket] = useState<WebSocket | null>(null);
  const [messages, setMessages] = useState<string[]>([]);

  useEffect(() => {
    const ws = new WebSocket(url);
    
    ws.onopen = () => {
      if (isMounted()) {
        setSocket(ws);
      }
    };

    ws.onmessage = (event) => {
      if (isMounted()) {
        setMessages(prev => [...prev, event.data]);
      }
    };

    ws.onclose = () => {
      if (isMounted()) {
        setSocket(null);
      }
    };

    // Register cleanup for WebSocket
    addCleanup(() => {
      if (ws.readyState === WebSocket.OPEN) {
        ws.close();
      }
    });

    return () => {
      ws.close();
    };
  }, [url, isMounted, addCleanup]);

  const sendMessage = useCallback((message: string) => {
    if (socket && socket.readyState === WebSocket.OPEN) {
      socket.send(message);
    }
  }, [socket]);

  return { socket, messages, sendMessage };
}

API Reference

useCleanup type signaturetypescript
function useCleanup(): {
  // Core functions
  isMounted: () => boolean;
  addCleanup: (cleanup: () => void) => void;
  createAbortController: () => AbortController;
  nullifyRef: <T>(ref: React.MutableRefObject<T>) => void;

  // Management functions
  removeAbortController: (controller: AbortController) => void;
  removeCleanup: (cleanup: () => void) => boolean;
  triggerCleanup: () => void;

  // Debugging
  getCleanupStats: () => {
    isMounted: boolean;
    cleanupFunctions: number;
    abortControllers: number;
    refsToNullify: number;
  };
}

Key Features

isMounted
Prevents state updates after component unmount
addCleanup
Register cleanup functions for automatic execution
createAbortController
Automatic AbortController management with cleanup
nullifyRef
Prevents memory leaks by nullifying refs on unmount
StrictMode
Compatible with React StrictMode (effects run twice)
Best Practices
Recommended patterns for using tryError hooks effectively

1. Error Boundaries Integration

Combining hooks with error boundariestypescript
import { TryErrorBoundary } from '@try-error/react';

function App() {
  return (
    <TryErrorBoundary
      fallback={(error, errorInfo, retry) => (
        <ErrorPage 
          error={error} 
          onRetry={retry}
          details={errorInfo}
        />
      )}
    >
      <Router>
        <Routes>
          <Route path="/users" element={<UsersPage />} />
          <Route path="/profile" element={<ProfilePage />} />
        </Routes>
      </Router>
    </TryErrorBoundary>
  );
}

2. Custom Hook Composition

Creating reusable custom hookstypescript
// Custom hook for API operations
function useApi<T>(endpoint: string) {
  return useTry(
    async () => {
      const result = await tryAsync(() => 
        fetch(endpoint).then(r => {
          if (!r.ok) throw new Error(`HTTP ${r.status}`);
          return r.json();
        })
      );
      
      if (isTryError(result)) throw result;
      return result;
    },
    [endpoint],
    { retryCount: 2, retryDelay: 1000 }
  );
}

// Usage
function UsersList() {
  const { data: users, error, loading } = useApi<User[]>('/api/users');
  
  // Component logic...
}

3. Form Validation Pattern

Form handling with tryErrortypescript
function useFormValidation<T>(validationSchema: (data: T) => TryResult<T>) {
  const [data, setData] = useState<T>({} as T);
  const [errors, setErrors] = useState<Record<string, string>>({});

  const { execute: validate, loading } = useTryCallback(
    async (formData: T) => {
      const result = validationSchema(formData);
      
      if (isTryError(result)) {
        setErrors({ general: result.message });
        throw result;
      }
      
      setErrors({});
      return result;
    },
    {
      onError: (error) => {
        if (error.context && typeof error.context === 'object') {
          setErrors(error.context as Record<string, string>);
        }
      }
    }
  );

  return { data, setData, errors, validate, loading };
}

✅ New React Hooks Available

We've added commonly requested React hooks for better integration:

useTryState

State management with built-in error handling

useTryMutation

Mutations with loading states (like React Query)

useFormMutation

Specialized hook for form submissions

useStateWithError

Simple state with error tracking

useTryState Hook
State management with built-in error handling for state updates

Basic Usage

useTryState exampletypescript
function UserProfile() {
  const [user, setUser, error, clearError] = useTryState<User | null>(null);

  const updateUser = useCallback((newUser: User) => {
    setUser(() => {
      // This can throw - error will be caught automatically
      return validateAndTransformUser(newUser);
    });
  }, [setUser]);

  return (
    <div>
      {error && (
        <div className="error">
          {error.message}
          <button onClick={clearError}>Clear</button>
        </div>
      )}
      
      <UserForm 
        user={user} 
        onSubmit={updateUser}
      />
    </div>
  );
}

API Reference

useTryState type signaturetypescript
function useTryState<T>(
  initialValue: T
): [
  T,                                              // current state value
  (updater: T | ((current: T) => T)) => void,    // state setter with error handling
  TryError | null,                               // current error (if any)
  () => void                                     // clear error function
]
useTryMutation Hook
Mutation handling with loading states, similar to React Query patterns

Basic Usage

useTryMutation exampletypescript
function CreateUserForm() {
  const { mutate, isLoading, error, data } = useTryMutation(
    async (userData: CreateUserData) => {
      const response = await fetch('/api/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(userData)
      });
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      return response.json();
    },
    {
      onSuccess: (user) => {
        toast.success(`User ${user.name} created!`);
        router.push(`/users/${user.id}`);
      },
      onError: (error) => {
        toast.error(error.message);
      }
    }
  );

  const handleSubmit = (formData: CreateUserData) => {
    mutate(formData);
  };

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      handleSubmit(getFormData(e.currentTarget));
    }}>
      {/* form fields */}
      
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Creating...' : 'Create User'}
      </button>
      
      {error && <div className="error">{error.message}</div>}
      {data && <div className="success">User created: {data.name}</div>}
    </form>
  );
}

API Reference

useTryMutation type signaturetypescript
function useTryMutation<T, TVariables = void>(
  mutationFn: (variables: TVariables) => Promise<T>,
  options?: {
    onSuccess?: (data: T) => void;
    onError?: (error: TryError) => void;
    onSettled?: () => void;
  }
): {
  data: T | null;
  error: TryError | null;
  isLoading: boolean;
  isSuccess: boolean;
  isError: boolean;
  mutate: (variables: TVariables) => Promise<void>;
  mutateAsync: (variables: TVariables) => Promise<TryResult<T, TryError>>;
  reset: () => void;
}
useFormMutation Hook
Specialized hook for form submissions with automatic FormData handling

Basic Usage

useFormMutation exampletypescript
function ContactForm() {
  const { submitForm, isSubmitting, submitError, submitData } = useFormMutation(
    async (formData: FormData) => {
      const response = await fetch('/api/contact', {
        method: 'POST',
        body: formData
      });
      
      return response.json();
    },
    {
      onSuccess: () => {
        toast.success('Message sent successfully!');
      }
    }
  );

  return (
    <form onSubmit={submitForm}>
      <input name="name" placeholder="Your name" required />
      <input name="email" type="email" placeholder="Your email" required />
      <textarea name="message" placeholder="Your message" required />
      
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Sending...' : 'Send Message'}
      </button>
      
      {submitError && (
        <div className="error">{submitError.message}</div>
      )}
      
      {submitData && (
        <div className="success">Message sent! ID: {submitData.id}</div>
      )}
    </form>
  );
}