What are Hooks?
Hooks are functions that let you “hook into” Preact state and lifecycle features from function components. They make it easier to reuse stateful logic between components and organize your code.Available Hooks
Preact provides all the standard hooks from thepreact/hooks package:
- useState - Manage local state
- useEffect - Perform side effects
- useReducer - Alternative to useState for complex state
- useRef - Create a mutable ref object
- useMemo - Memoize expensive computations
- useCallback - Memoize callback functions
- useContext - Access context values
- useLayoutEffect - Run effects synchronously after DOM mutations
- useImperativeHandle - Customize ref exposure
- useDebugValue - Display custom hook labels in DevTools
- useErrorBoundary - Catch errors in components
- useId - Generate unique IDs for accessibility
useState
TheuseState hook lets you add state to function components. It returns an array with the current state value and a function to update it.
Functional Updates
When the new state depends on the previous state, pass a function to the setter:useEffect
TheuseEffect hook lets you perform side effects in function components. Effects run after the browser paints, making them non-blocking.
import { useState, useEffect } from 'preact/hooks';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// Cleanup function
return () => clearInterval(interval);
}, []); // Empty array = run once on mount
return <div>Seconds: {seconds}</div>;
}
The second argument to
useEffect is a dependency array. The effect re-runs when dependencies change:function UserData({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]); // Re-run when userId changes
return user ? <div>{user.name}</div> : <div>Loading...</div>;
}
useReducer
For complex state logic,useReducer is often preferable to useState. It’s built on top of useState internally.
Lazy Initialization
useReducer accepts an optional third argument for lazy initialization:
Rules of Hooks
Hooks have two important rules that must be followed:- Rule 1: Top Level Only
- Rule 2: Function Components Only
Only call hooks at the top level of your function. Don’t call hooks inside loops, conditions, or nested functions.
Custom Hooks
Custom hooks let you extract component logic into reusable functions. They’re just functions that use other hooks.Best Practices
// Good
const [isLoading, setIsLoading] = useState(false);
const [userData, setUserData] = useState(null);
// Less clear
const [flag, setFlag] = useState(false);
const [data, setData] = useState(null);
// Good: Separate effects for separate concerns
function Component({ userId }) {
useEffect(() => {
// Handle user data fetching
}, [userId]);
useEffect(() => {
// Handle analytics tracking
}, []);
}
// Less ideal: One effect doing too much
function Component({ userId }) {
useEffect(() => {
// Fetching, tracking, and more...
}, [userId]);
}
// Before: Repeated pattern
function ComponentA() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/a').then(r => r.json()).then(setData);
}, []);
}
function ComponentB() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/b').then(r => r.json()).then(setData);
}, []);
}
// After: Custom hook
function useFetch(url) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url).then(r => r.json()).then(setData);
}, [url]);
return data;
}
function ComponentA() {
const data = useFetch('/api/a');
}
function ComponentB() {
const data = useFetch('/api/b');
}
Implementation Details
Preact’s hooks are implemented inhooks/src/index.js and integrate deeply with Preact’s rendering system. Key implementation details:
- Hooks use an internal
currentComponentvariable to track which component is rendering - Each hook call increments a
currentIndexcounter to maintain hook state between renders useStateis implemented usinguseReducerinternally- Effects are queued and flushed after painting using
requestAnimationFrame - Hook state is stored in
component.__hooks._listarray
hooks/src/index.js:172-175
Next Steps
Context API
Learn how to share state across components using Context
Refs
Access DOM elements and persist values with refs