Skip to content

Preact Context — Sharing State Across the Component Tree

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Preact Context. We cover key concepts, practical examples, and best practices to help you master this topic.

Learn Preact Context API to share state across components without prop drilling, using createContext and useContext in the 3kB Preact framework.

In this lesson, you'll understand how to create context, provide values, and consume them in any descendant component without passing props through intermediaries.

What You'll Learn

How to use createContext to define shared state, Provider to supply values, and useContext to consume them in functional components.

Why It Matters

Props-only data flow requires every intermediate component to forward props, even if they don't use them. Context eliminates this boilerplate and makes deep data sharing clean.

Real-World Use

Doda Browser's extension uses context to share the current user session, theme preference, and language setting across 20+ components without threading props through every level.

flowchart TD
    A[App Provider] -->|Theme Context| B[Header]
    A -->|Theme Context| C[Sidebar]
    A -->|Theme Context| D[Main Content]
    A -->|Theme Context| E[Footer]
    B --> F[useContext Theme]
    C --> G[useContext Theme]
    D --> H[useContext Theme]
    style A fill:#673ab8,color:#fff
    style F fill:#4a148c,color:#fff
    style H fill:#4a148c,color:#fff

Creating Context

Define context with a default value:

import { createContext } from 'preact';

const ThemeContext = createContext('light');
const UserContext = createContext(null);

The argument to createContext is the default value used when a component reads context outside a Provider.

Providing Context

Wrap the component tree with a Provider:

import { createContext } from 'preact';
import { useState } from 'preact/hooks';

const ThemeContext = createContext('light');
const UserContext = createContext(null);

function App() {
  const [theme, setTheme] = useState('light');
  const user = { name: 'Alice', role: 'admin' };

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <UserContext.Provider value={user}>
        <Header />
        <MainContent />
        <Footer />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

Output: Any component inside the ThemeContext.Provider and UserContext.Provider can access theme and user data without receiving them as props.

Consuming Context with useContext

Access context values in functional components:

import { useContext } from 'preact/hooks';

function Header() {
  const { theme, setTheme } = useContext(ThemeContext);
  const user = useContext(UserContext);

  return (
    <header style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}>
      <span>Welcome, {user?.name}</span>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle {theme === 'light' ? 'Dark' : 'Light'} Mode
      </button>
    </header>
  );
}

Output: The header shows the user's name and a theme toggle button. Changing the theme updates the header's colors and any other component consuming ThemeContext.

Context with Signals

Combine context with signals for efficient updates:

import { createContext } from 'preact';
import { signal } from '@preact/signals';

const CartContext = createContext();

function CartProvider({ children }) {
  const cart = signal([]);
  const total = computed(() =>
    cart.value.reduce((sum, item) => sum + item.price * item.qty, 0)
  );

  const addItem = (item) => {
    cart.value = [...cart.value, { ...item, qty: 1 }];
  };

  const removeItem = (id) => {
    cart.value = cart.value.filter(item => item.id !== id);
  };

  return (
    <CartContext.Provider value={{ cart, total, addItem, removeItem }}>
      {children}
    </CartContext.Provider>
  );
}

function CartBadge() {
  const { cart } = useContext(CartContext);
  // Only the badge text updates when cart changes — no re-render
  return <span>Cart: {cart.value.length} items</span>;
}

Output: The cart badge reads a signal from context. When items are added or removed, only the badge text node updates, not the entire component.

Multiple Contexts

Components can consume multiple contexts:

const ThemeContext = createContext('light');
const LanguageContext = createContext('en');
const AuthContext = createContext(null);

function SettingsPanel() {
  const { theme, setTheme } = useContext(ThemeContext);
  const { lang, setLang } = useContext(LanguageContext);
  const user = useContext(AuthContext);

  if (!user) return <p>Please log in to change settings.</p>;

  return (
    <div>
      <select value={theme} onChange={e => setTheme(e.target.value)}>
        <option value="light">Light</option>
        <option value="dark">Dark</option>
      </select>
      <select value={lang} onChange={e => setLang(e.target.value)}>
        <option value="en">English</option>
        <option value="es">Spanish</option>
      </select>
    </div>
  );
}

Output: The settings panel reads theme, language, and auth context simultaneously. Changes to any context update only the relevant parts.

Context vs Prop Drilling

Compare the two approaches:

// Prop drilling — every intermediate component must forward props
function App() { return <Header user={user} theme={theme} />; }
function Header({ user, theme }) { return <Nav user={user} theme={theme} />; }
function Nav({ user, theme }) { return <Avatar user={user} theme={theme} />; }
function Avatar({ user, theme }) { return <img src={user.avatar} className={theme} />; }

// Context — only the consuming component needs the data
function Avatar() {
  const user = useContext(UserContext);
  const theme = useContext(ThemeContext);
  return <img src={user.avatar} className={theme} />;
}

Output: With context, the Avatar component directly accesses user and theme without Header and Nav having to forward props they don't use.

Common Mistakes

  1. Putting everything in one context: Splitting unrelated state (theme, auth, cart) into separate contexts prevents unnecessary re-renders when one part changes.
  2. Creating a new context value every render: value={{ theme, setTheme }} creates a new object every render, causing all consumers to re-render. Use useMemo or signals.
  3. Using context for localized state: Context is for state shared across many components. Local component state should stay in useState or useReducer.
  4. Reading context outside a Provider: Components that read context without a parent Provider get the default value. This can hide bugs if the default isn't meaningful.
  5. Overusing context: Not everything needs context. If data only flows through 2-3 levels, prop drilling is simpler and more explicit.

Practice Questions

  1. What does createContext return? Answer: A context object with Provider and Consumer properties. The Provider wraps the component tree to supply values.

  2. How do you read context in a functional component? Answer: Call useContext(ContextObject) from preact/hooks. It returns the current context value.

  3. What happens if you read context without a matching Provider? Answer: The component gets the default value passed to createContext(defaultValue).

  4. Why should unrelated state be in separate contexts? Answer: When a context value changes, all consumers re-render. Separate contexts prevent unrelated state changes from triggering unnecessary re-renders.

Challenge

Create a multi-language (i18n) system using context: a LanguageContext provides the current language and a t(key) translation function. Create a nested component tree that demonstrates translation without prop drilling.

Mini Project

Build a user authentication system with context: AuthContext provides user data, login/logout functions, and loading state. Protect certain routes by consuming the auth context in a route guard component.

FAQ

Does Preact context work with `shouldComponentUpdate`?

: Yes. Class components can use contextType or Context.Consumer for render props. Context updates respect shouldComponentUpdate.

Can I update context from a nested component?

: Yes. Pass a setter function or dispatch function through context. The child calls it to update the parent's state.

Is Preact context the same as React context?

: Largely yes, with minor differences. Preact's createContext returns an object with Provider and Consumer. The useContext hook works identically.

Does context cause entire tree re-renders?

: In Preact without signals, all context consumers re-render when the value changes. With signals as context values, only specific DOM nodes update.

What's Next

Learn about Preact Refs and DOM Access to understand how to directly access DOM elements and integrate with imperative APIs.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro