Skip to main content

Custom Hooks Overview

DevJobs uses custom React hooks to encapsulate complex logic and promote code reusability across components. These hooks follow React’s hooks conventions and provide clean abstractions for common patterns.

Available Hooks

DevJobs includes three custom hooks:

useFilters

Manages job filtering, pagination, and API calls. This hook handles:
  • URL-synchronized filter state
  • Paginated job data fetching
  • Search text with proper URL synchronization
  • Loading states and error handling
View useFilters documentation

useRouter

A lightweight wrapper around React Router’s navigation hooks. Provides:
  • Programmatic navigation
  • Current path tracking
  • Simplified API for route changes
View useRouter documentation

useSearchForm

Handles search form state and user interactions. Features:
  • Debounced text input (500ms delay)
  • Form submission handling
  • URL parameter synchronization
  • Ref management for form elements
  • Clear filters functionality
View useSearchForm documentation

When to Use Custom Hooks

Use custom hooks when:
  • Reusing stateful logic: Multiple components need the same state management pattern
  • Separating concerns: Complex logic should be extracted from component rendering
  • Coordinating effects: Multiple useEffect calls need to work together
  • Managing side effects: API calls, URL synchronization, or external integrations

Hook Composition Patterns

DevJobs hooks are designed to work together:

Search Page Pattern

The Search page combines useFilters and useSearchForm:
import { useFilters } from '@/hooks/useFilters.js'

function SearchPage() {
  const {
    jobs,
    loading,
    total,
    totalPages,
    currentPage,
    textToFilter,
    handlePageChange,
    handleSearch,
    handleTextFilter,
  } = useFilters()

  return (
    <main>
      <SearchFormSection 
        initialTextInput={textToFilter} 
        onSearch={handleSearch} 
        onTextFilter={handleTextFilter} 
      />
      <JobListings jobs={jobs} />
      <Pagination 
        currentPage={currentPage} 
        totalPages={totalPages} 
        onPageChange={handlePageChange} 
      />
    </main>
  )
}
The Home page uses useRouter for programmatic navigation:
import useRouter from "@/hooks/useRouter"

export function HomePage() {
  const { navigateTo } = useRouter()

  const handleSearch = (event) => {
    event.preventDefault()
    const formData = new FormData(event.currentTarget)
    const searchText = formData.get('search')
    const url = searchText 
      ? `/search?text=${encodeURIComponent(searchText)}` 
      : '/search'
    navigateTo(url)
  }

  return (
    <form onSubmit={handleSearch}>
      {/* form fields */}
    </form>
  )
}

Best Practices

1. Keep Hooks Focused

Each hook should have a single, well-defined responsibility. useFilters handles all filtering logic, while useRouter only manages navigation.

2. URL Synchronization

Hooks like useFilters and useSearchForm keep application state in sync with URL parameters, enabling:
  • Shareable URLs
  • Browser back/forward navigation
  • Page refresh persistence

3. Controlled Debouncing

useSearchForm implements debouncing to reduce API calls:
const handleTextChange = (event) => {
  const text = event.target.value
  setSearchText(text)

  if (timeoutId.current) {
    clearTimeout(timeoutId.current)
  }

  timeoutId.current = setTimeout(() => {
    onTextFilter(text)
  }, 500) // Wait 500ms before triggering search
}

4. Prop Callbacks

Hooks return stable callback functions that components can pass down:
const { handlePageChange, handleSearch, handleTextFilter } = useFilters()

// These callbacks won't change between renders
<Pagination onPageChange={handlePageChange} />

Hook Dependencies

DevJobs hooks depend on:
  • React: useState, useEffect, useRef, useId
  • React Router: useSearchParams, useNavigate, useLocation
All hooks are located in /src/hooks/ and can be imported using the @/hooks/ alias.

Next Steps

Build docs developers (and LLMs) love