🎓 What You Will Learn
- useState: Managing component state with multiple values
- useEffect: Side effects and cleanup with dependencies
- useReducer: Complex state logic with actions
- useContext: Avoiding prop drilling with global state
- useCallback: Memoize callback functions for performance
- useMemo: Memoize expensive computations
- Custom Hooks: Create reusable hook logic
- Hook Rules: When and where to use hooks correctly
What Are React Hooks?
Hooks are special functions that let you use React features in functional components. They "hook into" React state and lifecycle features without writing class components.
Before hooks, you had to use class components to access state and lifecycle. Hooks changed everything by making these features available in functional components.
useState — Multiple State Values
useState lets you add state to functional components. You can use it multiple times to manage different pieces of state.
import { useState } from 'react';
export function UserForm() {
// Each useState call creates separate state
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [age, setAge] = useState("");
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value)}
placeholder="Age"
/>
</div>
);
}Name: Alice
Age: 25
Active: ✅ Yes
useEffect — Side Effects & Cleanup
useEffect lets you perform side effects in components. Side effects include fetching data, updating document title, setting up subscriptions, etc.
useEffect(() => {
// This runs after every render
console.log("Component rendered!");
});
useEffect(() => {
// This runs only once (on mount)
console.log("Component mounted!");
}, []);
useEffect(() => {
// This runs when count changes
console.log("Count changed:", count);
}, [count]);useEffect(() => {
// Setup
const timer = setInterval(() => {
console.log("Timer running");
}, 1000);
// Cleanup function
return () => {
clearInterval(timer);
console.log("Cleanup!");
};
}, []);Count: 0
👆 Check browser tab title - it updates with useEffect!
useReducer — Complex State Logic
useReducer is for managing complex state with multiple related values. It's like useState but with more control.
const initialState = { count: 0, step: 1 };
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + state.step };
case 'SET_STEP':
return { ...state, step: action.payload };
default:
return state;
}
}
// Usage
const [state, dispatch] = useReducer(reducer, initialState);
<button onClick={() => dispatch({ type: 'INCREMENT' })}>
Increment by {state.step}
</button>Count: 0
Step: 1
useContext — Avoiding Prop Drilling
useContext lets you access global state without passing props down through every component (prop drilling).
import { createContext, useContext } from 'react';
// 1. Create context
const ThemeContext = createContext();
// 2. Create provider
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. Use context in any component
function Button() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(...)}>
Current theme: {theme}
</button>
);
}Current Theme: ☀️ Light
useCallback — Memoize Callbacks
useCallback memoizes a function. It's useful when passing callbacks to optimized child components that rely on reference equality.
import { useCallback, useState } from 'react';
export function Parent() {
const [count, setCount] = useState(0);
// handleClick will be same reference unless count changes
const handleClick = useCallback(() => {
console.log("Clicked! Count is:", count);
}, [count]); // Re-create only when count changes
return <Child onClick={handleClick} />;
}Count: 0
Result: 0
Component Renders: 0
useMemo — Memoize Values
useMemo memoizes a computed value. Use it for expensive calculations that shouldn't rerun on every render.
import { useMemo, useState } from 'react';
export function ExpensiveComponent() {
const [count, setCount] = useState(0);
// This calculation only runs when count changes
const expensiveValue = useMemo(() => {
console.log("Calculating...");
return count * count * count; // Expensive!
}, [count]);
return <p>Result: {expensiveValue}</p>;
}Count: 0
Result: 0
✨ Calculation memoized - only recalculates when count changes
Custom Hooks — Reusable Logic
Custom hooks are JavaScript functions that use other hooks. They let you extract component logic into reusable functions.
// Custom Hook
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
});
const setStorageValue = (value) => {
setValue(value);
localStorage.setItem(key, JSON.stringify(value));
};
return [value, setStorageValue];
}
// Usage in any component
function App() {
const [name, setName] = useLocalStorage('name', 'Guest');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}use, can use other hooks, called at top level only.Saved Name: Guest
💾 Persisted in localStorage
Rules of Hooks (Critical!)
// WRONG: Conditional hooks
if (isCondition) {
const [count, setCount] = useState(0); // ❌
}
// RIGHT: Always call at top level
const [count, setCount] = useState(0);// WRONG: Regular JavaScript function
function regularFunction() {
const [count, setCount] = useState(0); // ❌
}
// RIGHT: Component or custom hook
function MyComponent() {
const [count, setCount] = useState(0); // ✅
}
function useCustomHook() {
const [count, setCount] = useState(0); // ✅
}// WRONG: Missing dependency
useEffect(() => {
console.log(count); // Uses 'count' but not in dependencies
}, []);
// RIGHT: Include all dependencies
useEffect(() => {
console.log(count);
}, [count]);All Built-in Hooks Reference
| Hook | Purpose | Example |
|---|---|---|
| useState | Add state | const [x, setX] = useState(0) |
| useEffect | Side effects | useEffect(() => {...}, [deps]) |
| useContext | Read context | const x = useContext(Context) |
| useReducer | Complex state | const [state, dispatch] = useReducer(...) |
| useRef | DOM reference | const ref = useRef() |
| useCallback | Memoize function | const fn = useCallback(..., [deps]) |
| useMemo | Memoize value | const val = useMemo(..., [deps]) |
| useLayoutEffect | Before paint | useLayoutEffect(() => {...}) |
Best Practices
Common Hook Patterns
useEffect(() => {
let isMounted = true;
fetch('/api/data')
.then(res => res.json())
.then(data => {
if (isMounted) {
setData(data);
}
});
return () => {
isMounted = false; // Cleanup
};
}, []); // Run once on mountconst [search, setSearch] = useState("");
useEffect(() => {
const timer = setTimeout(() => {
// Perform search
console.log("Searching for:", search);
}, 500);
return () => clearTimeout(timer); // Cleanup
}, [search]); // Run when search changesCommon Mistakes
// WRONG: Memory leak
useEffect(() => {
window.addEventListener('resize', handleResize);
// Missing cleanup!
}, []);
// RIGHT: Cleanup listeners
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);// WRONG: count is always 0
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1); // count is always 0!
}, 1000);
return () => clearInterval(timer);
}, []); // Missing count dependency
// RIGHT: Use functional update
useEffect(() => {
const timer = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
return () => clearInterval(timer);
}, []);What's Next?
- 1. State Management: Context API patterns, Redux, Zustand
- 2. Performance Optimization: Profiling, React.memo, code splitting
- 3. API Integration: Fetching, caching, error handling
- 4. Testing Hooks: React Testing Library for hooks
- 5. Advanced Patterns: Higher-Order Components, Render Props
Master hooks through practice! 🚀 Build real projects using these hooks and you'll become proficient quickly. Start with useState and useEffect, then move to useReducer and custom hooks.