| Hook | Purpose | When to Use | Return Value |
|---|---|---|---|
useState |
Manage local state | Component needs to track data | [state, setState] |
useEffect |
Side effects | API calls, subscriptions, DOM updates | void (cleanup function) |
useContext |
Consume context | Access global state/theme/auth | Context value |
useReducer |
Complex state logic | Multiple state updates together | [state, dispatch] |
useCallback |
Memoize functions | Prevent unnecessary re-renders | Memoized function |
useMemo |
Memoize values | Expensive calculations | Memoized value |
useRef |
Mutable ref object | DOM access, persist values | Ref object |
useLayoutEffect |
Sync layout effects | DOM measurements | void (cleanup function) |
useImperativeHandle |
Expose ref methods | Parent needs child methods | void |
useDebugValue |
DevTools label | Custom hooks debugging | void |
Manages local component state
const [state, setState] = useState(initialValue);// Simple counter
const [count, setCount] = useState(0);
// Object state
const [user, setUser] = useState({
name: '',
email: ''
});
// Lazy initial state
const [data, setData] = useState(() => {
return expensiveComputation();
});Handles side effects in components
useEffect(() => {
// Effect logic
return () => {
// Cleanup (optional)
};
}, [dependencies]);// API call on mount
useEffect(() => {
fetchData();
}, []); // Empty array = run once
// Watch for changes
useEffect(() => {
console.log('User changed:', user);
}, [user]);
// Cleanup subscription
useEffect(() => {
const subscription = subscribe();
return () => subscription.unsubscribe();
}, []);Consumes React context values
const value = useContext(MyContext);// Theme context
const ThemeContext = createContext();
// Provider component
function App() {
return (
<ThemeContext.Provider value="dark">
<Child />
</ThemeContext.Provider>
);
}
// Consumer component
function Child() {
const theme = useContext(ThemeContext);
return <div className={theme}>...</div>;
}Complex state management with actions
const [state, dispatch] = useReducer(reducer, initialState);const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(
reducer,
initialState
);
return (
<button onClick={() => dispatch({type: 'increment'})}>
Count: {state.count}
</button>
);
}Memoizes callback functions
const memoizedCallback = useCallback(
() => { /* function */ },
[dependencies]
);// Prevent child re-renders
const handleClick = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// Optimized event handler
const SearchBar = ({ onSearch }) => {
const [query, setQuery] = useState('');
const handleSearch = useCallback(() => {
onSearch(query);
}, [query, onSearch]);
return (
<>
<input onChange={e => setQuery(e.target.value)} />
<button onClick={handleSearch}>Search</button>
</>
);
};Memoizes expensive computations
const memoizedValue = useMemo(
() => computeExpensiveValue(a, b),
[a, b]
);// Expensive calculation
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => {
return sum + item.value * item.quantity;
}, 0);
}, [items]);
// Filter/sort optimization
const sortedList = useMemo(() => {
return [...items].sort((a, b) => b.price - a.price);
}, [items]);
// Complex object creation
const chartData = useMemo(() => ({
labels: data.map(d => d.label),
values: data.map(d => d.value),
colors: generateColors(data.length)
}), [data]);Creates mutable ref object
const refContainer = useRef(initialValue);// DOM element reference
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
// Persist value across renders
const renderCount = useRef(0);
renderCount.current++;
// Store previous value
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}Customizes ref exposed to parent
useImperativeHandle(ref, () => ({
// exposed methods
}), [deps]);// Child component
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = '';
}
}));
return <input ref={inputRef} />;
});
// Parent component
const parentRef = useRef();
parentRef.current.focus();
parentRef.current.clear();Synchronous DOM mutations
useLayoutEffect(() => {
// Synchronous effect
}, [deps]);// Measure DOM element
function MeasureExample() {
const [height, setHeight] = useState(0);
const divRef = useRef();
useLayoutEffect(() => {
setHeight(divRef.current.getBoundingClientRect().height);
});
return (
<div ref={divRef}>
Height: {height}px
</div>
);
}
// Prevent flicker
useLayoutEffect(() => {
// DOM updates here won't cause visual flicker
element.style.opacity = 1;
});Display label in React DevTools
useDebugValue(value, formatFn?);// Custom hook with debug info
function useUser(userId) {
const [user, setUser] = useState(null);
// Display in DevTools
useDebugValue(user ? `User: ${user.name}` : 'Loading...');
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
return user;
}
// With formatter
useDebugValue(date, date => date.toDateString());function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
const setValue = value => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
const json = await response.json();
setData(json);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => {
setValue(v => !v);
}, []);
return [value, toggle];
}
// Usage
const [isOpen, toggleOpen] = useToggle();
const [isDark, toggleTheme] = useToggle(true);function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
// Usage
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearch = useDebounce(searchTerm, 500);| Pattern | Code Example | Use Case |
|---|---|---|
| Conditional Effects | useEffect(() => { if (condition) { /* effect */ } }, [condition]) |
Run effect based on condition |
| Previous Value | const prevValue = useRef(); useEffect(() => { prevValue.current = value }) |
Track previous prop/state |
| Mount Only | useEffect(() => { /* once */ }, []) |
Run once on mount |
| Unmount Cleanup | useEffect(() => { return () => { /* cleanup */ } }, []) |
Cleanup on unmount |
| Force Update | const [, forceUpdate] = useReducer(x => x + 1, 0) |
Force re-render |
| Safe State Update | setState(prev => prev + 1) |
Update based on previous |
| Lazy Initial State | useState(() => expensiveOperation()) |
Expensive initial value |