Skip to main content

Custom Hooks

Overview

Collection of custom React hooks untuk mempermudah state management, data handling, dan logic reuse di aplikasi frontend. Hooks ini dirancang untuk reusable, type-safe, dan framework-agnostic.

Komponen Hooks

useFilterSortHandler

Universal hook untuk managing filters, sorting, dan pagination dalam table/list views.

Features:

  • Multiple filter operators (eq, like, in, between, gt, gte, lt, lte)
  • Sort status management (ascending/descending)
  • Pagination support (page & perPage)
  • Query string generation untuk API calls
  • Active filter/sort detection
  • TypeScript generics support
  • URL encoding
  • Date formatting

Use Cases:

  • Data tables dengan server-side filtering
  • Search functionality dengan multiple criteria
  • Admin dashboards dengan complex filters
  • Paginated list views
import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler'

function UserTable() {
const {
filters,
setFilter,
removeFilter,
sortStatus,
setSortStatus,
page,
setPage,
perPage,
setPerPage,
queryString
} = useFilterSortHandler({
initialPage: 1,
initialPerPage: 10
})

// Set filter
setFilter('name', 'like', 'John')

// Generate query string untuk API
const url = `/api/users?${queryString}` // ?name[like]=John&page=1&perPage=10
}

Filter Handler

Helper functions untuk filter dan sort operations.

Features:

  • Filter transformation
  • Sort transformation
  • Query building
  • Type conversion
  • Date handling

Use Cases:

  • Custom filter logic
  • Transform filter data
  • Build complex queries
import { buildFilterQuery, transformSortStatus } from '@/utils/hooks/filter-sort-handler'

const filterQuery = buildFilterQuery(filters)
const sortQuery = transformSortStatus(sortStatus)

Sort Handler

Dedicated hook untuk sort operations.

Features:

  • Sort column management
  • Sort direction toggle
  • Multi-column sorting
  • Sort state persistence

Use Cases:

  • Table column sorting
  • List sorting
  • Custom sort implementations
import { useSortHandler } from '@/utils/hooks/sort-handler'

const { sortBy, sortDirection, handleSort } = useSortHandler('name', 'asc')

// Toggle sort
handleSort('email') // Sort by email

Common Patterns

Table dengan Filter dan Sort

import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler'
import { DataTable } from 'mantine-datatable'

function DataTableWithFilters() {
const {
filters,
setFilter,
removeFilter,
sortStatus,
setSortStatus,
page,
setPage,
perPage,
setPerPage,
queryString,
hasActiveFilters
} = useFilterSortHandler({
initialPage: 1,
initialPerPage: 10,
initialSort: { columnAccessor: 'name', direction: 'asc' }
})

// Fetch data dengan query string
const { data, isLoading } = useQuery({
queryKey: ['users', queryString],
queryFn: () => fetchUsers(queryString)
})

return (
<div>
{/* Filter Section */}
<div className="filters">
<input
placeholder="Search by name"
onChange={(e) => setFilter('name', 'like', e.target.value)}
/>
<select onChange={(e) => setFilter('status', 'eq', e.target.value)}>
<option value="">All Status</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
{hasActiveFilters && (
<button onClick={removeFilter}>Clear Filters</button>
)}
</div>

{/* Table */}
<DataTable
records={data?.records}
columns={[
{ accessor: 'name', sortable: true },
{ accessor: 'email', sortable: true },
{ accessor: 'status' }
]}
sortStatus={sortStatus}
onSortStatusChange={setSortStatus}
page={page}
onPageChange={setPage}
recordsPerPage={perPage}
onRecordsPerPageChange={setPerPage}
totalRecords={data?.total}
fetching={isLoading}
/>
</div>
)
}

Advanced Filtering

import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler'

function AdvancedSearch() {
const { setFilter, getFilterValue, queryString } = useFilterSortHandler()

// Range filter (between)
const handleDateRange = (start: Date, end: Date) => {
setFilter('created_at', 'between', [start, end])
}

// Multiple values (in)
const handleMultiSelect = (values: string[]) => {
setFilter('category', 'in', values)
}

// Greater than
const handleMinPrice = (price: number) => {
setFilter('price', 'gte', price)
}

// Get current filter value
const currentName = getFilterValue('name') // Returns current filter value

return (
<div>
<input
value={currentName || ''}
onChange={(e) => setFilter('name', 'like', e.target.value)}
/>
{/* Other filters */}
</div>
)
}

Persist Filters in URL

import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler'
import { useSearchParams } from 'react-router-dom'

function PersistentFilters() {
const [searchParams, setSearchParams] = useSearchParams()

const { queryString, filters, sortStatus, page } = useFilterSortHandler({
initialPage: Number(searchParams.get('page')) || 1,
initialPerPage: Number(searchParams.get('perPage')) || 10
})

// Update URL when filters change
useEffect(() => {
setSearchParams(queryString)
}, [queryString])

// Users can share or bookmark filtered URLs
return <div>...</div>
}

Type Safety

Hooks fully support TypeScript dengan generics:

interface User {
id: number
name: string
email: string
status: 'active' | 'inactive'
created_at: Date
}

const {
filters,
setFilter,
sortStatus
} = useFilterSortHandler<User>()

// Type-safe filter keys
setFilter('name', 'like', 'John') // ✅ OK
setFilter('invalid', 'like', 'test') // ❌ TypeScript error

Best Practices

1. Initial State

Set reasonable initial values:

const hook = useFilterSortHandler({
initialPage: 1,
initialPerPage: 25, // Reasonable default
initialSort: { columnAccessor: 'created_at', direction: 'desc' }
})

2. Debounce Text Input

Untuk search/filter text, gunakan debounce:

import { useDebouncedValue } from '@mantine/hooks'

function SearchFilter() {
const [search, setSearch] = useState('')
const [debouncedSearch] = useDebouncedValue(search, 300)
const { setFilter } = useFilterSortHandler()

useEffect(() => {
setFilter('name', 'like', debouncedSearch)
}, [debouncedSearch])

return <input value={search} onChange={(e) => setSearch(e.target.value)} />
}

3. Clear Filters

Provide cara untuk clear filters:

const { removeFilter, hasActiveFilters } = useFilterSortHandler()

{hasActiveFilters && (
<button onClick={removeFilter}>
Clear All Filters
</button>
)}

4. Loading States

Show loading saat fetching data:

const { queryString } = useFilterSortHandler()
const { data, isLoading } = useQuery(['data', queryString], fetchData)

return (
<div>
{isLoading && <Spinner />}
<DataTable records={data} />
</div>
)

5. Error Handling

Handle errors gracefully:

const { data, error, isError } = useQuery(['data', queryString], fetchData)

if (isError) {
return <ErrorMessage error={error} />
}

Performance Tips

1. Memoization

Hook sudah menggunakan useMemo dan useCallback internally untuk optimasi.

2. Lazy Loading

// Only load data when filters change
const { queryString } = useFilterSortHandler()

const { data } = useQuery({
queryKey: ['data', queryString],
queryFn: () => fetchData(queryString),
enabled: !!queryString // Only fetch when queryString exists
})

3. Pagination

Implement proper pagination untuk large datasets:

const { page, perPage, setPage, setPerPage } = useFilterSortHandler({
initialPerPage: 50 // Larger page size for better performance
})

Integration Examples

With React Query

import { useQuery } from '@tanstack/react-query'
import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler'

const { queryString } = useFilterSortHandler()

const { data } = useQuery({
queryKey: ['users', queryString],
queryFn: () => fetch(`/api/users?${queryString}`).then(r => r.json())
})

With Mantine DataTable

import { DataTable } from 'mantine-datatable'
import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler'

const { sortStatus, setSortStatus, page, setPage } = useFilterSortHandler()

<DataTable
sortStatus={sortStatus}
onSortStatusChange={setSortStatus}
page={page}
onPageChange={setPage}
/>

With Form Libraries

import { useForm } from 'react-hook-form'
import { useFilterSortHandler } from '@/utils/hooks/useFilterSortHandler'

const form = useForm()
const { setFilter } = useFilterSortHandler()

form.watch((values) => {
Object.entries(values).forEach(([key, value]) => {
if (value) setFilter(key, 'eq', value)
})
})

Technology Stack: React Hooks, TypeScript
Use Case: Data filtering, Sorting, Pagination, State management
Last Updated: November 2025