Skip to main content

Overview

DevJobs is built with a modern React architecture that emphasizes:
  • Component-based design - Modular, reusable UI components
  • Custom hooks - Encapsulated state and logic management
  • URL-driven state - Search params synchronized with application state
  • Lazy loading - Code-split routes for optimal performance

Directory Structure

The application follows a clear, organized structure:
src/
├── App.jsx                 # Main application with routing
├── main.jsx                # Entry point with BrowserRouter
├── components/             # Reusable UI components
│   ├── ErrorBoundary/      # Error handling components
│   ├── Footer/             # Footer component
│   ├── Header/             # Header with navigation
│   ├── JobCard/            # Individual job display
│   ├── JobDetailSkeleton/  # Loading skeleton for job details
│   ├── JobListings/        # Job list container
│   ├── Link/               # Custom Link wrapper
│   ├── Loading/            # Loading indicators
│   ├── PageSkeleton/       # Full page loading state
│   ├── Pagination/         # Pagination controls
│   ├── SearchFormSection/  # Search form with filters
│   └── index.js            # Component exports
├── hooks/                  # Custom React hooks
│   ├── useFilters.js       # Filter and pagination state
│   ├── useRouter.js        # Navigation utilities
│   └── useSearchForm.js    # Form state management
├── pages/                  # Page-level components
│   ├── Home.jsx            # Landing page
│   ├── Search.jsx          # Job search page
│   ├── Detail.jsx          # Job detail page
│   └── 404.jsx             # Not found page
└── assets/                 # Static assets

Component Hierarchy

The application structure follows this hierarchy:
App (Root)
├── ErrorBoundary
│   ├── Header
│   ├── Routes (Suspense wrapper)
│   │   ├── HomePage
│   │   ├── SearchPage
│   │   │   ├── SearchFormSection
│   │   │   ├── JobListings
│   │   │   │   └── JobCard (multiple)
│   │   │   └── Pagination
│   │   ├── JobDetailPage
│   │   └── NotFoundPage
│   └── Footer

Key Components

Layout Components

  • Header: Navigation and branding
  • Footer: Site footer information
  • ErrorBoundary: Error handling wrapper

Page Components

  • HomePage: Landing page with search
  • SearchPage: Job listings with filters
  • DetailPage: Individual job details
  • NotFoundPage: 404 error page

Feature Components

  • SearchFormSection: Filter controls
  • JobListings: Job list container
  • JobCard: Individual job display
  • Pagination: Page navigation

UI Components

  • Link: Router-aware link wrapper
  • Loading: Loading indicators
  • PageSkeleton: Page-level skeleton
  • JobDetailSkeleton: Detail skeleton

Data Flow Patterns

DevJobs uses a unidirectional data flow pattern:

1. URL as Single Source of Truth

1

URL Parameters

Search filters and pagination state are stored in URL search parameters
2

Hook Initialization

Custom hooks read initial state from URL parameters on mount
3

State Updates

User interactions update local state and synchronize back to URL
4

API Requests

URL parameters trigger API requests via useEffect hooks

2. State Management Flow

Example Flow

Here’s how data flows when a user filters jobs:
  1. User Action: User selects a technology filter
  2. Event Handler: handleSearch is called with new filter values
  3. State Update: setFilters updates local state
  4. URL Sync: useEffect syncs filters to URL via setSearchParams
  5. API Trigger: Another useEffect detects filter change and fetches jobs
  6. UI Update: Component re-renders with new job results

Key Architectural Decisions

Storing state in URL parameters provides:
  • Shareable links: Users can share filtered search results
  • Browser history: Back/forward buttons work naturally
  • Bookmarkable: Users can bookmark specific searches
  • Deep linking: Direct access to any application state
Custom hooks offer:
  • Local scope: State is localized to components that need it
  • Better performance: No unnecessary re-renders from global context
  • URL synchronization: Natural fit for URL-based state
  • Simpler testing: Easier to test isolated hook logic
Route-based code splitting provides:
  • Faster initial load: Only load code for the current page
  • Better performance: Smaller initial bundle size
  • Progressive loading: Load features as users navigate
  • Built-in loading states: Suspense handles loading UI automatically

Component Communication

Components communicate through three primary patterns:

Props Down

Parent components pass data and callbacks to children:
// SearchPage passes handlers to SearchFormSection
<SearchFormSection 
  initialTextInput={textToFilter}
  onSearch={handleSearch} 
  onTextFilter={handleTextFilter} 
/>

Custom Hooks

Shared logic is extracted into reusable hooks:
// Multiple components can use the same hook
const { navigateTo } = useRouter()
const { jobs, loading, handleSearch } = useFilters()

URL Parameters

State is shared via URL search parameters:
// One component writes to URL
setSearchParams({ technology: 'react', page: '2' })

// Another component reads from URL
const tech = searchParams.get('technology')

Performance Optimizations

Code Splitting

Routes are lazy-loaded to reduce initial bundle size
const SearchPage = lazy(() => 
  import('./pages/Search.jsx')
)

Debounced Input

Text search uses 500ms debounce to reduce API calls
setTimeout(() => {
  onTextFilter(text)
}, 500)

Skeleton Loading

Loading states use skeleton screens for better UX
<Suspense fallback={<PageSkeleton />}>
  <Routes>...</Routes>
</Suspense>

Centralized Exports

Components are exported from index.js for cleaner imports
import { Header, Footer } from '@/components'

Next Steps

Routing

Learn about React Router setup and navigation patterns

State Management

Explore custom hooks and URL synchronization

Build docs developers (and LLMs) love