Tutorial React Hooks Lengkap dari useState hingga Custom Hooks Advanced untuk Tahun 2026
Evolusi React Hooks di Tahun 2026
Sejak diperkenalkan di React 16.8, React Hooks telah merevolusi cara kita menulis functional components. Di tahun 2026, hooks telah menjadi fundamental building block dalam React development ecosystem, dengan puluhan custom hooks dan patterns yang telah matang dan menjadi industry standards.
React Hooks memungkinkan kita untuk menggunakan state dan lifecycle features dalam functional components tanpa perlu class components. Ini bukan sekadar syntax sugar, melainkan paradigma shift yang memungkinkan kode yang lebih reusable, testable, dan maintainable.
Studi terbaru menunjukkan bahwa 92% React developers lebih memilih hooks dibandingkan class components untuk new projects, dengan alasan utama: better code organization, easier testing, dan superior performance.

Fundamental Hooks: Dasar-dasar yang Wajib Dikuasai
useState Hook: State Management Simplified
useState adalah hook paling fundamental yang memungkinkan functional components untuk memiliki local state.
import { useState } from 'react';
function Counter() {
// Basic useState dengan primitive value
const [count, setCount] = useState(0);
// useState dengan object state
const [user, setUser] = useState({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
// useState dengan array
const [todos, setTodos] = useState([]);
// useState dengan lazy initialization
const [expensiveState, setExpensiveState] = useState(() => {
return calculateExpensiveValue(); // Hanya dijalankan sekali
});
const increment = () => {
// Functional update untuk prev state dependency
setCount(prevCount => prevCount + 1);
};
const updateUserName = (newName) => {
// Immutable update untuk object state
setUser(prevUser => ({
...prevUser,
name: newName
}));
};
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
</div>
);
}
Advanced useState patterns:
// State reducer pattern untuk complex state
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'UPDATE_FIELD':
return { ...state, [action.field]: action.value };
case 'RESET':
return initialState;
default:
return state;
}
}, initialState);
// Custom hook untuk object state management
function useObjectState(initialState) {
const [state, setState] = useState(initialState);
const updateField = (field, value) => {
setState(prev => ({ ...prev, [field]: value }));
};
const resetState = () => {
setState(initialState);
};
return [state, updateField, resetState];
}
useEffect Hook: Side Effects Management
useEffect menangani side effects seperti data fetching, subscriptions, dan DOM manipulations.
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Effect dengan dependency array
useEffect(() => {
let isCancelled = false;
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (!isCancelled) {
setUser(data);
setError(null);
}
} catch (err) {
if (!isCancelled) {
setError(err.message);
setUser(null);
}
} finally {
if (!isCancelled) {
setLoading(false);
}
}
};
fetchUser();
// Cleanup function
return () => {
isCancelled = true;
};
}, [userId]); // Re-run hanya jika userId berubah
// Effect tanpa dependencies (runs every render)
useEffect(() => {
document.title = user ? user.name : 'Loading...';
});
// Effect dengan empty dependency array (runs once)
useEffect(() => {
console.log('Component mounted');
return () => {
console.log('Component unmounted');
};
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{user.name}</div>;
}
useContext Hook: Global State Management
useContext menyediakan cara untuk share data antar components tanpa prop drilling.
import { createContext, useContext, useState } from 'react';
// Create context
const ThemeContext = createContext();
// Theme provider component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
const value = {
theme,
toggleTheme,
colors: theme === 'light' ? {
background: '#ffffff',
text: '#000000'
} : {
background: '#000000',
text: '#ffffff'
}
};
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
// Custom hook untuk theme consumption
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// Consumer component
function ThemeButton() {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
style={{ background: theme === 'light' ? '#333' : '#fff' }}
>
Current theme: {theme}
</button>
);
}
Advanced Hooks untuk Performance Optimization
useMemo Hook: Expensive Calculations Caching
useMemo meng-expensive calculations dan cached hasilnya untuk performance optimization.
import { useState, useMemo } from 'react';
function ExpensiveCalculation({ numbers }) {
const [filter, setFilter] = useState('');
// Memoized expensive calculation
const sortedNumbers = useMemo(() => {
console.log('Running expensive sort...');
return numbers
.filter(num => num > 0)
.sort((a, b) => a - b)
.slice(0, 100); // Limit hasil untuk performance
}, [numbers]);
// Memoized filtered results
const filteredNumbers = useMemo(() => {
console.log('Filtering numbers...');
return sortedNumbers.filter(num =>
num.toString().includes(filter)
);
}, [sortedNumbers, filter]);
// Memoized calculated statistics
const statistics = useMemo(() => {
if (filteredNumbers.length === 0) return null;
const sum = filteredNumbers.reduce((acc, num) => acc + num, 0);
const mean = sum / filteredNumbers.length;
const median = calculateMedian(filteredNumbers);
const std = calculateStandardDeviation(filteredNumbers);
return { sum, mean, median, std };
}, [filteredNumbers]);
return (
<div>
<input
type="text"
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="Filter numbers..."
/>
{statistics && (
<div>
<p>Sum: {statistics.sum}</p>
<p>Mean: {statistics.mean.toFixed(2)}</p>
<p>Median: {statistics.median}</p>
<p>Std Dev: {statistics.std.toFixed(2)}</p>
</div>
)}
<ul>
{filteredNumbers.map(num => (
<li key={num}>{num}</li>
))}
</ul>
</div>
);
}
useCallback Hook: Function Reference Stabilization
useCallback memoizes functions untuk prevent unnecessary re-renders.
import { useState, useCallback, memo } from 'react';
// Memoized child component
const TodoItem = memo(({ todo, onToggle, onDelete }) => {
console.log(`Rendering TodoItem: ${todo.id}`);
return (
<div>
<span
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
onClick={() => onToggle(todo.id)}
>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</div>
);
});
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React Hooks', completed: false },
{ id: 2, text: 'Build awesome app', completed: true }
]);
const [filter, setFilter] = useState('all');
// Memoized event handlers
const toggleTodo = useCallback((id) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
}, []);
const deleteTodo = useCallback((id) => {
setTodos(prevTodos =>
prevTodos.filter(todo => todo.id !== id)
);
}, []);
const addTodo = useCallback((text) => {
setTodos(prevTodos => [
...prevTodos,
{ id: Date.now(), text, completed: false }
]);
}, []);
// Memoized filtered todos
const filteredTodos = useMemo(() => {
switch (filter) {
case 'active':
return todos.filter(todo => !todo.completed);
case 'completed':
return todos.filter(todo => todo.completed);
default:
return todos;
}
}, [todos, filter]);
return (
<div>
<TodoForm onAdd={addTodo} />
<FilterButtons currentFilter={filter} setFilter={setFilter} />
{filteredTodos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
))}
</div>
);
}
useReducer Hook: Complex State Management
useReducer ideal untuk complex state logic yang melibatkan multiple sub-values atau next state bergantung pada previous state.
import { useReducer } from 'react';
// Reducer function untuk shopping cart
const cartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
const existingItem = state.items.find(item => item.id === action.payload.id);
if (existingItem) {
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: item.quantity + 1 }
: item
)
};
} else {
return {
...state,
items: [...state.items, { ...action.payload, quantity: 1 }]
};
}
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload)
};
case 'UPDATE_QUANTITY':
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: action.payload.quantity }
: item
)
};
case 'CLEAR_CART':
return {
...state,
items: []
};
case 'APPLY_COUPON':
return {
...state,
coupon: action.payload,
discount: calculateDiscount(action.payload, state.subtotal)
};
default:
return state;
}
};
// Cart provider component
function CartProvider({ children }) {
const initialState = {
items: [],
subtotal: 0,
discount: 0,
total: 0,
coupon: null
};
const [state, dispatch] = useReducer(cartReducer, initialState);
// Calculate totals whenever items or discount changes
const totals = useMemo(() => {
const subtotal = state.items.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
const total = Math.max(0, subtotal - state.discount);
return { subtotal, total };
}, [state.items, state.discount]);
const cartActions = {
addItem: (item) => dispatch({ type: 'ADD_ITEM', payload: item }),
removeItem: (itemId) => dispatch({ type: 'REMOVE_ITEM', payload: itemId }),
updateQuantity: (itemId, quantity) =>
dispatch({ type: 'UPDATE_QUANTITY', payload: { id: itemId, quantity } }),
clearCart: () => dispatch({ type: 'CLEAR_CART' }),
applyCoupon: (couponCode) =>
dispatch({ type: 'APPLY_COUPON', payload: couponCode })
};
const value = {
...state,
...totals,
...cartActions
};
return (
<CartContext.Provider value={value}>
{children}
</CartContext.Provider>
);
}
Custom Hooks: Reusable Logic Abstraction
Building Custom Hooks Best Practices
Custom hooks adalah JavaScript functions yang namanya diawali dengan “use” dan dapat memanggil hooks lain.
// Custom hook untuk data fetching
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isCancelled = false;
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (!isCancelled) {
setData(result);
setError(null);
}
} catch (err) {
if (!isCancelled) {
setError(err.message);
setData(null);
}
} finally {
if (!isCancelled) {
setLoading(false);
}
}
};
fetchData();
return () => {
isCancelled = true;
};
}, [url]);
const refetch = useCallback(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url, options);
const result = await response.json();
setData(result);
setError(null);
} catch (err) {
setError(err.message);
setData(null);
} finally {
setLoading(false);
}
};
fetchData();
}, [url, options]);
return { data, loading, error, refetch };
}
// Usage example
function UserList() {
const { data: users, loading, error, refetch } = useFetch('/api/users');
if (loading) return <div>Loading users...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<button onClick={refetch}>Refresh</button>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
Advanced Custom Hooks Patterns
1. useLocalStorage Hook:
function useLocalStorage(key, initialValue) {
// Get initial value from localStorage or use default
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(`Error reading localStorage key "${key}":`, error);
return initialValue;
}
});
// Function to update value in localStorage and state
const setValue = useCallback((value) => {
try {
// Allow value to be a function so we have the same API as useState
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
// Save to localStorage
if (typeof window !== 'undefined') {
window.localStorage.setItem(key, JSON.stringify(valueToStore));
}
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error);
}
}, [key, storedValue]);
// Function to remove item from localStorage
const removeValue = useCallback(() => {
try {
setStoredValue(initialValue);
if (typeof window !== 'undefined') {
window.localStorage.removeItem(key);
}
} catch (error) {
console.error(`Error removing localStorage key "${key}":`, error);
}
}, [key, initialValue]);
return [storedValue, setValue, removeValue];
}
// Usage
function ThemeChanger() {
const [theme, setTheme, removeTheme] = useLocalStorage('theme', 'light');
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
<button onClick={removeTheme}>
Reset to Default
</button>
</div>
);
}
2. useDebounce Hook:
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// Usage untuk search input
function SearchComponent() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
const { data: searchResults } = useFetch(
`/api/search?q=${encodeURIComponent(debouncedSearchTerm)}`
);
return (
<div>
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
<SearchResults results={searchResults} />
</div>
);
}
3. useInfiniteScroll Hook:
function useInfiniteScroll(fetchMore, hasMore) {
const [loading, setLoading] = useState(false);
useEffect(() => {
const handleScroll = () => {
if (loading || !hasMore) return;
const { scrollTop, clientHeight, scrollHeight } = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 1000) {
setLoading(true);
fetchMore().finally(() => setLoading(false));
}
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [loading, hasMore, fetchMore]);
return loading;
}
// Usage
function InfinitePostList() {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const loadMorePosts = useCallback(async () => {
const newPosts = await fetchPosts(page);
if (newPosts.length === 0) {
setHasMore(false);
} else {
setPosts(prevPosts => [...prevPosts, ...newPosts]);
setPage(prevPage => prevPage + 1);
}
}, [page]);
const loading = useInfiniteScroll(loadMorePosts, hasMore);
return (
<div>
{posts.map(post => (
<Post key={post.id} post={post} />
))}
{loading && <div>Loading more posts...</div>}
{!hasMore && <div>No more posts</div>}
</div>
);
}
Performance Optimization Strategies
React DevTools Profiler Integration
Hooking into React DevTools untuk performance monitoring:
import { Profiler } from 'react';
function ProfiledComponent({ children, id }) {
const onRender = (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
console.log('Performance metrics:', {
id,
phase, // "mount" or "update"
actualDuration, // Time spent rendering
baseDuration, // Estimated time without memoization
startTime,
commitTime
});
// Send metrics ke monitoring service
if (actualDuration > 100) { // Threshold dalam ms
sendPerformanceAlert({
componentId: id,
duration: actualDuration,
phase
});
}
};
return (
<Profiler id={id} onRender={onRender}>
{children}
</Profiler>
);
}
// Usage
function App() {
return (
<div>
<ProfiledComponent id="UserProfile">
<UserProfile />
</ProfiledComponent>
<ProfiledComponent id="TodoList">
<TodoList />
</ProfiledComponent>
</div>
);
}
Virtual Scrolling dengan Hooks
Optimized rendering untuk large datasets:
function useVirtualScroll(items, containerHeight, itemHeight) {
const [scrollTop, setScrollTop] = useState(0);
const visibleStart = Math.floor(scrollTop / itemHeight);
const visibleEnd = Math.min(
visibleStart + Math.ceil(containerHeight / itemHeight) + 1,
items.length
);
const visibleItems = items.slice(visibleStart, visibleEnd);
const offsetY = visibleStart * itemHeight;
const handleScroll = useCallback((e) => {
setScrollTop(e.target.scrollTop);
}, []);
return {
visibleItems,
offsetY,
handleScroll
};
}
// Virtual scroll component
function VirtualList({ items, containerHeight, itemHeight }) {
const { visibleItems, offsetY, handleScroll } = useVirtualScroll(
items,
containerHeight,
itemHeight
);
return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={handleScroll}
>
<div style={{ height: items.length * itemHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, index) => (
<div
key={item.id}
style={{
height: itemHeight,
borderBottom: '1px solid #eee'
}}
>
{item.content}
</div>
))}
</div>
</div>
</div>
);
}
Testing Hooks dengan React Testing Library
Unit Testing Custom Hooks
Testing patterns untuk custom hooks:
import { renderHook, act } from '@testing-library/react';
import { useFetch } from './useFetch';
// Mock fetch
global.fetch = jest.fn();
describe('useFetch', () => {
beforeEach(() => {
fetch.mockClear();
});
test('should return data on successful fetch', async () => {
const mockData = { id: 1, name: 'Test User' };
fetch.mockResolvedValueOnce({
ok: true,
json: async () => mockData
});
const { result } = renderHook(() => useFetch('/api/user/1'));
expect(result.current.loading).toBe(true);
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(result.current.loading).toBe(false);
expect(result.current.data).toEqual(mockData);
expect(result.current.error).toBe(null);
});
test('should handle fetch error', async () => {
const errorMessage = 'Network error';
fetch.mockRejectedValueOnce(new Error(errorMessage));
const { result } = renderHook(() => useFetch('/api/user/1'));
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(result.current.loading).toBe(false);
expect(result.current.error).toBe(errorMessage);
expect(result.current.data).toBe(null);
});
test('should refetch data when refetch is called', async () => {
fetch
.mockResolvedValueOnce({
ok: true,
json: async () => ({ id: 1, name: 'User 1' })
})
.mockResolvedValueOnce({
ok: true,
json: async () => ({ id: 1, name: 'User 2' })
});
const { result } = renderHook(() => useFetch('/api/user/1'));
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(result.current.data.name).toBe('User 1');
await act(async () => {
result.current.refetch();
});
expect(result.current.data.name).toBe('User 2');
});
});
Kesimpulan dan Best Practices untuk 2026
React Hooks telah menjadi fundamental building block dalam modern React development. Dengan memahami patterns dan best practices ini, Anda dapat build aplikasi yang performant, maintainable, dan scalable.
Key Takeaways:
- Start with basic hooks - Master useState, useEffect, dan useContext dulu
- Use useMemo dan useCallback judiciously - Profile dulu sebelum optimizing
- Build custom hooks - Abstract reusable logic untuk code organization
- Test your hooks - Use React Testing Library untuk comprehensive testing
- Monitor performance - Use DevTools Profiler untuk optimization decisions
Future Outlook:
- Concurrent Features - useTransition dan useDeferredValue akan semakin penting
- Server Components - New patterns untuk server-side rendering dengan hooks
- AI-Powered Development - Tools untuk automated hook optimization
- Better DevTools - Enhanced debugging dan performance monitoring
Dengan menguasai React Hooks patterns di tahun 2026, Anda akan siap untuk menghadapi tantangan development yang lebih kompleks dan build aplikasi yang truly performant dan maintainable.
Link Postingan : https://www.tirinfo.com/tutorial-react-hooks-lengkap-usestate-custom-hooks-advanced-2026/