Preact Hooks Overview — useState, useEffect and More in 3kB
In this tutorial, you will learn about Preact Hooks Overview. We cover key concepts, practical examples, and best practices to help you master this topic.
Learn Preact's hooks API: useState for state management, useEffect for side effects, and how Preact's hooks differ from React hooks in behavior and implementation.
In this lesson, you'll explore the core hooks in preact/hooks, understand their rules, and see how they enable state and lifecycle in functional components.
What You'll Learn
How to use useState, useEffect, useReducer, useRef, useMemo, and useCallback in Preact, including Preact-specific behavior differences.
Why It Matters
Hooks eliminate the need for class components in most cases. They make stateful logic reusable across components without changing the component hierarchy.
Real-World Use
DodaZIP's file extraction progress bar uses useState for progress percentage, useEffect to subscribe to extraction events, and useRef to access the canvas element for custom rendering.
flowchart TD
A[useState] --> B[Component State]
C[useEffect] --> D[Side Effects]
E[useReducer] --> F[Complex State]
G[useRef] --> H[DOM Refs]
I[useMemo] --> J[Memoized Values]
K[useCallback] --> L[Memoized Callbacks]
style A fill:#673ab8,color:#fff
style C fill:#673ab8,color:#fff
useState
The most basic hook for local component state:
import { useState } from 'preact/hooks';
function Counter() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
return (
<div>
<p>Count: {count}</p>
<p>Step: {step}</p>
<button onClick={() => setCount(c => c + step)}>Add</button>
<button onClick={() => setStep(s => s + 1)}>Increase Step</button>
</div>
);
}
Output: Clicking "Add" increments count by the current step. Clicking "Increase Step" increments the step itself. The functional updater c => c + step ensures correct values.
useEffect
Handles side effects: data fetching, subscriptions, DOM manipulation:
import { useState, useEffect } from 'preact/hooks';
function WindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
// Cleanup function
return () => window.removeEventListener('resize', handleResize);
}, []); // Empty array = run only on mount and unmount
return <p>Window width: {width}px</p>;
}
Output: Displays the current window width and updates it when the browser resizes. The cleanup removes the event listener on unmount.
useRef
Access DOM elements or store mutable values that persist across renders:
import { useRef, useEffect } from 'preact/hooks';
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
// Focus the input when the component mounts
inputRef.current.focus();
}, []);
return <input ref={inputRef} type="text" placeholder="Auto-focused" />;
}
Output: The input is focused automatically when the page loads. useRef provides a stable reference that persists between renders without causing re-renders.
useMemo and useCallback
Optimize performance by memoizing values and functions:
import { useState, useMemo, useCallback } from 'preact/hooks';
function ExpensiveList({ items, filter }) {
const filteredItems = useMemo(() => {
console.log('Filtering items...');
return items.filter(item => item.includes(filter));
}, [items, filter]); // Only recompute when items or filter change
const handleClick = useCallback((item) => {
console.log('Clicked:', item);
}, []); // Stable function identity
return (
<ul>
{filteredItems.map(item => (
<li key={item} onClick={() => handleClick(item)}>{item}</li>
))}
</ul>
);
}
Output: The filter only recomputes when items or filter changes. The handleClick function has a stable identity across renders.
useReducer
For complex state logic with multiple sub-values:
import { useReducer } from 'preact/hooks';
const initialState = { count: 0, step: 1 };
function reducer(state, action) {
switch (action.type) {
case 'increment': return { ...state, count: state.count + state.step };
case 'decrement': return { ...state, count: state.count - state.step };
case 'setStep': return { ...state, step: action.payload };
case 'reset': return initialState;
default: return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count} (step: {state.step})</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<input type="number" value={state.step}
onChange={e => dispatch({ type: 'setStep', payload: +e.target.value })} />
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
Output: A counter with configurable step size. Complex state transitions are centralized in the reducer function.
Common Mistakes
- Missing dependency arrays in
useEffect: Without specifying dependencies, effects run after every render, causing infinite loops. Always include all variables used inside the effect. - Calling hooks conditionally: Hooks must be called in the same order every render. Don't put hooks inside
ifstatements or loops. - Using stale closures: If an effect captures a state value, it sees the value from that render. Use the functional updater form or add the value to dependencies.
- Over-memoizing with
useMemo: Preact is fast enough that unnecessaryuseMemocalls add overhead. Only memoize when profiling shows an issue. - Forgetting that
useRefdoesn't trigger re-renders: Changingref.currentwon't cause the component to re-render. Use state for values that affect the UI.
Practice Questions
What is the purpose of the dependency array in
useEffect? Answer: It tells Preact when to re-run the effect. An empty array[]means run once on mount. Omitting it means run after every render.What does
useRefreturn? Answer: A mutable object with a.currentproperty that persists across renders. Changing it doesn't trigger re-renders.What is the difference between
useMemoanduseCallback? Answer:useMemomemoizes a computed value.useCallbackmemoizes a function reference.useCallback(fn, deps)is equivalent touseMemo(() => fn, deps).What happens if you call a hook inside a conditional? Answer: Preact (like React) requires hooks to be called in the same order every render. Conditional hooks violate this rule and cause bugs.
Challenge
Build a custom hook useLocalStorage(key, initialValue) that persists state to localStorage and syncs across tabs using the storage event.
Mini Project
Create a todo list application with useReducer for state management, useEffect to persist todos to localStorage, and useRef for auto-focusing the new todo input.
FAQ
What's Next
Learn about Preact Hooks — useReducer, useRef, useMemo, useCallback for deeper coverage of advanced hooks in Preact.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro