Hireable LogoHireable
Frontend

Custom Hooks

Reusable React hooks for state management, API calls, and common patterns

Overview

The application provides a set of custom hooks for common patterns. All hooks are exported from @/hooks.

import {
  useApi,
  useAuth,
  useDebounce,
  useForm,
  useMediaQuery,
  useScrollDirection,
  useWindowSize,
} from "@/hooks";

useAuth

Manages authentication state and provides login/logout methods.

import { useAuth } from "@/hooks";
 
function LoginForm() {
  const {
    user,
    isAuthenticated,
    isLoading,
    error,
    login,
    signup,
    logout,
    forgotPassword,
    resetPassword,
    clearError,
  } = useAuth();
 
  const handleLogin = async () => {
    try {
      await login({ email: "user@example.com", password: "password" });
    } catch (err) {
      // Error is also available in `error` state
    }
  };
 
  if (isLoading) return <Spinner />;
  if (isAuthenticated) return <Dashboard user={user} />;
 
  return <LoginForm onSubmit={handleLogin} error={error} />;
}

Auth State

PropertyTypeDescription
userUser | nullCurrent authenticated user
isAuthenticatedbooleanWhether user is logged in
isLoadingbooleanLoading state
errorstring | nullError message

Auth Methods

MethodParametersDescription
loginLoginRequestLogin with email/password
signupSignupRequestRegister new user
logout-Log out current user
forgotPasswordForgotPasswordRequestRequest password reset
resetPasswordResetPasswordRequestReset password with token
clearError-Clear error state
refreshSession-Refresh access token

useApi

Generic hook for API calls with loading, error, caching, and retry support.

import { useApi } from "@/hooks";
import { jobsService } from "@/features/jobs";
 
function JobsList() {
  const {
    data: jobs,
    isLoading,
    error,
    isSuccess,
    execute,
    reset,
  } = useApi(jobsService.getAll, {
    immediate: true,      // Execute on mount
    cacheKey: "jobs",     // Cache results
    cacheDuration: 5000,  // 5 seconds
    retries: 2,           // Retry on failure
    retryDelay: 1000,     // 1 second between retries
  });
 
  if (isLoading) return <Spinner />;
  if (error) return <Error message={error} />;
 
  return (
    <ul>
      {jobs?.map(job => <JobCard key={job.id} job={job} />)}
    </ul>
  );
}

Options

OptionTypeDefaultDescription
immediatebooleanfalseExecute on mount
cacheKeystring-Key for caching results
cacheDurationnumber300000Cache TTL in ms (5 min)
retriesnumber0Number of retry attempts
retryDelaynumber1000Delay between retries in ms

Cache Utilities

import { clearApiCache, clearCacheKey } from "@/hooks";
 
// Clear all cached data
clearApiCache();
 
// Clear specific cache key
clearCacheKey("jobs");

useForm

Lightweight form management with validation.

import { useForm, validators } from "@/hooks";
 
function SignupForm() {
  const {
    values,
    errors,
    touched,
    isSubmitting,
    isValid,
    handleChange,
    handleBlur,
    handleSubmit,
    setFieldValue,
    resetForm,
  } = useForm({
    initialValues: {
      email: "",
      password: "",
      confirmPassword: "",
    },
    validationRules: {
      email: [
        validators.required("Email is required"),
        validators.email("Invalid email format"),
      ],
      password: [
        validators.required("Password is required"),
        validators.minLength(8, "Password must be at least 8 characters"),
        validators.password(),
      ],
      confirmPassword: [
        validators.required("Please confirm password"),
        validators.match("password", "Passwords do not match"),
      ],
    },
    onSubmit: async (values) => {
      await authService.signup(values);
    },
  });
 
  return (
    <form onSubmit={handleSubmit}>
      <Input
        name="email"
        value={values.email}
        onChange={handleChange}
        onBlur={handleBlur}
      />
      {touched.email && errors.email && <Error>{errors.email}</Error>}
      
      <Button type="submit" disabled={isSubmitting || !isValid}>
        {isSubmitting ? "Signing up..." : "Sign Up"}
      </Button>
    </form>
  );
}

Built-in Validators

import { validators } from "@/hooks";
 
validators.required("Field is required")
validators.email("Invalid email")
validators.minLength(8, "Too short")
validators.maxLength(100, "Too long")
validators.pattern(/regex/, "Invalid format")
validators.match("fieldName", "Fields must match")
validators.password("Password requirements not met")

useDebounce

Debounces a value for search inputs and API calls.

import { useDebounce } from "@/hooks";
 
function SearchInput() {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, 300);
 
  useEffect(() => {
    if (debouncedQuery) {
      searchApi(debouncedQuery);
    }
  }, [debouncedQuery]);
 
  return <Input value={query} onChange={(e) => setQuery(e.target.value)} />;
}

useMediaQuery

Responds to CSS media queries.

import { useMediaQuery } from "@/hooks";
 
function ResponsiveComponent() {
  const isMobile = useMediaQuery("(max-width: 768px)");
  const isTablet = useMediaQuery("(min-width: 769px) and (max-width: 1024px)");
  const isDesktop = useMediaQuery("(min-width: 1025px)");
 
  if (isMobile) return <MobileLayout />;
  if (isTablet) return <TabletLayout />;
  return <DesktopLayout />;
}

useWindowSize

Tracks window dimensions.

import { useWindowSize } from "@/hooks";
 
function ResponsiveCanvas() {
  const { width, height } = useWindowSize();
 
  return <canvas width={width} height={height} />;
}

useThrottledWindowSize

Throttled version for performance-sensitive use cases.

import { useThrottledWindowSize } from "@/hooks";
 
function HeavyVisualization() {
  const { width, height } = useThrottledWindowSize(100); // 100ms throttle
 
  return <ThreeJSCanvas width={width} height={height} />;
}

useScrollDirection

Detects scroll direction for hiding/showing headers.

import { useScrollDirection } from "@/hooks";
 
function Header() {
  const scrollDirection = useScrollDirection();
 
  return (
    <header className={scrollDirection === "down" ? "hidden" : "visible"}>
      <Navigation />
    </header>
  );
}

useAsyncError

Throws errors to error boundaries from async code.

import { useAsyncError } from "@/hooks";
 
function DataLoader() {
  const throwError = useAsyncError();
 
  useEffect(() => {
    fetchData().catch(throwError);
  }, []);
}

On this page