- EN
- ID
Master Table of Contents
- See full index in [01. Introduction]
Who this chapter is for
- Learners who already know React basics and are ready for real app logic
- Beginners who keep getting confused by
useEffectbehavior - Anyone who has seen duplicate requests, stale values, or weird rerender loops
What you’ll learn
- Better
useStatepatterns for object/array updates - Correct
useEffectusage and dependency thinking - How and when to extract reusable logic into custom hooks
Why this topic matters
Most production React bugs come from side effects, not JSX. Data fetching, timers, subscriptions, and browser sync all live in this area.
If you understand hooks deeply, your app becomes predictable. If not, you get classic issues: repeated API calls, stale UI, and hard-to-debug state behavior.
Core concepts
useState patterns
- Derived updates and object/array updates
- Avoid stale closure mistakes
Use functional updates (setState(prev => ...)) when the next value depends on previous state.
useEffect
- Runs after render
- Dependency array controls when effect runs
Quick mental model:
- No dependency array -> runs after every render
- Empty array (
[]) -> runs once on mount - With dependencies -> runs when those values change
Custom hooks
- Extract repeated state/effect logic
- Improve readability and reuse
A custom hook is not “advanced magic.” It is just a function that packages repeated hook logic cleanly.
Step-by-step walkthrough
Step 1 — Add effect-driven behavior
Start with one mount-time fetch and one explicit loading state.
Step 2 — Manage dependencies safely
Audit dependencies intentionally. Include what the effect really uses.
Step 3 — Extract into custom hook
Move repeated fetch + loading + error logic into a custom hook like useItems.
Target in this chapter: make side effects understandable and predictable.
Practical examples
Example 1 — Effect with fetch
useEffect(() => {
let active = true;
async function run() {
const response = await fetch("/api/items");
const data = await response.json();
if (active) setItems(data);
}
run();
return () => {
active = false;
};
}, []);
Why this matters:
- The cleanup guard helps prevent state updates from stale async responses.
Example 2 — Custom hook skeleton
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue((prev) => !prev);
return { value, toggle };
}
Real case:
- Hooks like this reduce repeated boilerplate in UI controls and settings panels.
Common mistakes and how to avoid them
- Missing dependencies in
useEffect-> stale data and inconsistent behavior - Putting too many concerns in one effect -> split effects by responsibility
- Repeating fetch logic across components -> extract custom hooks
- Triggering race conditions in search/fetch flows -> ignore stale responses or cancel old requests
Mini Project
- Build a live search app with:
- debounced input,
- request-based loading state,
- error fallback,
- stale-response protection.
Bonus:
- Move fetch logic into a
useSearchcustom hook.
Quick practice
- Add loading and error state to effect-based fetch
- Implement input debounce before request
- Extract request logic into one custom hook
- Explain in one sentence: when should one effect be split into two effects?
Key takeaways
- Hooks are powerful when logic is explicit and scoped well
useEffectis about synchronization, not random side-effect dumping- Custom hooks improve reuse and keep components focused
Next step
Continue to [11. Forms and Validation in React].
No Comments