Skip to content
ExponaBLOG
SVELTE & SVELTEKIT · DAY 014 / 100

Context API in Svelte for Svelte & SvelteKit developers

Share state within a component tree.

Expona Admin6 min read

Setting Up Context in Parent Components

When you need to pass state down through multiple levels of components without prop drilling, the Context API in Svelte for Svelte & SvelteKit developers becomes invaluable. Unlike prop drilling where you pass data through every intermediate component, context allows you to make values available to any descendant component in the tree.

The setContext function takes two arguments: a key (usually a symbol or string) and a value. This value can be anything—a primitive, an object, a store, or even a reactive object built with Svelte's reactive declarations.

Here's a practical example from an e-commerce application where we manage a shopping cart across multiple nested components:

typescript
// CartProvider.svelte
import { setContext } from 'svelte';
import { writable } from 'svelte/store';

const CART_KEY = Symbol('cart');

let items = writable([]);
let total = writable(0);

const addItem = (product) => {
  items.update(arr => [...arr, product]);
  recalculateTotal();
};

const removeItem = (productId) => {
  items.update(arr => arr.filter(p => p.id !== productId));
  recalculateTotal();
};

const recalculateTotal = () => {
  items.subscribe(arr => {
    const sum = arr.reduce((acc, p) => acc + p.price, 0);
    total.set(sum);
  });
};

setContext(CART_KEY, {
  items,
  total,
  addItem,
  removeItem
});

Notice we're exporting the CART_KEY symbol—this is crucial for type safety later. When you use setContext, it should happen during component initialization, typically at the top level of your component's script block.

Retrieving Context in Descendant Components

Any descendant component can now call getContext with the same key to access the context value. This works regardless of how deeply nested the component is in the tree.

In a CRM application, imagine a user management interface where a modal deep in the component tree needs to access user data set at the root:

typescript
// UserDetailModal.svelte
import { getContext } from 'svelte';

const USER_CONTEXT_KEY = Symbol('user');

const userContext = getContext(USER_CONTEXT_KEY);
const { currentUser, updateUser } = userContext;

const handleSave = async (newData) => {
  await updateUser(newData);
};

One important detail: getContext must be called during component initialization, not inside event handlers or reactive blocks. This is because context binding happens at compile time. If you call getContext after initialization, it will return undefined.

For optional context that might not exist, use getContext with a fallback:

typescript
const theme = getContext('theme-key') ?? 'light';

Server-Side Considerations and Hydration Safety

When building SvelteKit applications, context handling requires careful attention to the server-side rendering (SSR) phase. Context is component-tree-scoped and not shared across requests, which is actually a security feature—but it creates a critical gotcha.

During SSR, each request gets its own component instance tree. If you store request-specific data (like the current user from your database) in context, it won't carry over to the client automatically. Once the client hydrates, a fresh component tree is created, and your context values from the server are lost.

Here's how to handle this in a SvelteKit layout that needs to share user data:

typescript
// src/routes/+layout.svelte
import { setContext } from 'svelte';
import { page } from '$app/stores';

export let data;

// ✓ Good: Context is re-established on the client with data from page.data
setContext('user', {
  current: data.user,
  permissions: data.permissions
});

The key pattern: pass SSR-generated data through the data prop (which comes from load functions), then establish context with that data. This ensures both server and client have the same values during hydration.

Avoid storing anything that doesn't need to cross the SSR boundary in context. Use stores for client-only state that should persist across navigation.

Type-Safe Context Keys with Helper Functions

Spreading context keys throughout your codebase as magic strings leads to maintainability issues and runtime errors. A better pattern is to create a helper function that returns a typed context key. This gives you type safety and a single source of truth.

typescript
// contexts.ts
import type { Writable } from 'svelte/store';

interface CartContextValue {
  items: Writable<Array<{ id: string; price: number }>>;
  total: Writable<number>;
  addItem: (product: any) => void;
  removeItem: (productId: string) => void;
}

export function createCartContextKey() {
  return Symbol('cart-context') as unknown as CartContextValue;
}

export const CART_CONTEXT_KEY = createCartContextKey();

Now both parent and child components import from this central location:

typescript
// CartProvider.svelte
import { CART_CONTEXT_KEY } from './contexts';
import { setContext } from 'svelte';

setContext(CART_CONTEXT_KEY, { /* ... */ });

// ProductCard.svelte
import { CART_CONTEXT_KEY } from './contexts';
import { getContext } from 'svelte';

const cart = getContext(CART_CONTEXT_KEY);
// TypeScript knows cart has items, total, addItem, removeItem

This pattern prevents typos in keys, ensures type checking works correctly, and makes refactoring safer. If you rename the context structure, TypeScript will catch any mismatches immediately.

Common Mistakes and How to Avoid Them

Calling getContext outside initialization: This is the most frequent issue. Context must be retrieved during the script execution phase, not inside event handlers, reactive statements, or effects. If you need to update based on context later, store it in a variable during init.

Using context for global state across the entire app: Context is tree-scoped. If you're tempted to set application-wide state in a root layout context, you're fighting the design. Use a store instead, which is built for global client-side state.

Forgetting context doesn't serialize: Context exists only in the component tree. If you need state to persist across page reloads or navigation, use stores or session storage. Don't rely on context to survive a navigation event.

Leaking server data to the client accidentally: If your context contains sensitive data from a database query, ensure it's only set where appropriate. In SvelteKit, be explicit about what data flows from server to client through the data prop.

Creating new context objects on every render: If your context value is an object literal created in the script, it's recreated on every component update, breaking reactivity for child components. Create the context value once and update it via stores or reactive assignments.

Key Takeaways

  • Use setContext to establish values in a parent component and getContext to retrieve them in descendants, eliminating prop drilling for deeply nested trees.
  • Context is tree-scoped and request-scoped in SvelteKit—it doesn't persist across navigation or hydration boundaries automatically.
  • Combine context with stores to share reactive state; stores are better for global client-side state, context is better for tree-local concerns like theming or authorization.
  • Always call getContext during component initialization, not in event handlers or effects.
  • Create typed context keys in a central location using helper functions or constants to ensure type safety and prevent key mismatches.
  • Be cautious about what data you put in context on the server side; use SvelteKit's data prop to intentionally pass SSR data to context.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles