Skip to content

Preact Hooks Overview — useState, useEffect and More in 3kB

DodaTech Updated 2026-06-28 5 min read

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

  1. Missing dependency arrays in useEffect: Without specifying dependencies, effects run after every render, causing infinite loops. Always include all variables used inside the effect.
  2. Calling hooks conditionally: Hooks must be called in the same order every render. Don't put hooks inside if statements or loops.
  3. 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.
  4. Over-memoizing with useMemo: Preact is fast enough that unnecessary useMemo calls add overhead. Only memoize when profiling shows an issue.
  5. Forgetting that useRef doesn't trigger re-renders: Changing ref.current won't cause the component to re-render. Use state for values that affect the UI.

Practice Questions

  1. 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.

  2. What does useRef return? Answer: A mutable object with a .current property that persists across renders. Changing it doesn't trigger re-renders.

  3. What is the difference between useMemo and useCallback? Answer: useMemo memoizes a computed value. useCallback memoizes a function reference. useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

  4. 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

Does Preact support all React hooks?

: Preact supports useState, useEffect, useReducer, useRef, useMemo, useCallback, useContext, useImperativeHandle, useLayoutEffect, and useDebugValue. It does NOT support useInsertionEffect or useSyncExternalStore.

Are Preact hooks compatible with React hooks?

: Mostly yes, with minor differences. Preact's useEffect runs synchronously after paint (like useLayoutEffect in React), not asynchronously.

Can I write custom hooks in Preact?

: Yes. Custom hooks are regular JavaScript functions that use built-in hooks. The same composition rules apply.

Does Preact have `useDebugValue`?

: Yes, it's available in preact/hooks and works the same as React's for labeling custom hooks in DevTools.

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