Agent Skills: State Management Skill

Manage client-side state in Storefront Next using React context providers and feature-level Zustand stores. Use when handling basket/auth UI state, creating extension stores (for example store locator), or syncing client-visible state after server mutations. NOT for server-side data loading — see sfnext-data-fetching for loader patterns.

UncategorizedID: salesforcecommercecloud/b2c-developer-tooling/sfnext-state-management

Install this agent skill to your local

pnpm dlx add-skill https://github.com/SalesforceCommerceCloud/b2c-developer-tooling/tree/HEAD/skills/storefront-next/skills/sfnext-state-management

Skill Files

Browse the full folder contents for sfnext-state-management.

Download Skill

Loading file tree…

skills/storefront-next/skills/sfnext-state-management/SKILL.md

Skill Metadata

Name
sfnext-state-management
Description
Manage client-side state in Storefront Next using React context providers and feature-level Zustand stores. Use when handling basket/auth UI state, creating extension stores (for example store locator), or syncing client-visible state after server mutations. NOT for server-side data loading — see sfnext-data-fetching for loader patterns.

State Management Skill

This skill covers client-side state management in Storefront Next with server-first data loading.

Overview

Storefront Next keeps business data on the server (loader/action) and uses client state for UX continuity. In practice, app-wide state is commonly exposed through providers (for example auth/basket), while complex extension-specific state can use Zustand.

| State Type | Mechanism | Example | | --------------------- | ------------------------------ | ------------------------------------------------------ | | Server data | React Router loader | Product details, category listings | | App-wide client state | React context providers | Session display state, basket snapshot/hydration state | | Feature client state | Zustand store | Store locator modal/search state | | Mutations | React Router action (server) | Add to cart, update profile |

Zustand Store Pattern (Feature-Level)

// src/extensions/store-locator/stores/store-locator-store.ts
import {createStore} from 'zustand/vanilla';

type StoreLocatorState = {
  isOpen: boolean;
  mode: 'input' | 'device';
  selectedStoreInfo: SelectedStoreInfo | null;
};

type StoreLocatorActions = {
  open: () => void;
  close: () => void;
  setSelectedStoreInfo: (info: SelectedStoreInfo) => void;
};

type StoreLocatorStore = StoreLocatorState & StoreLocatorActions;

export const createStoreLocatorStore = (init?: Partial<StoreLocatorState>) => {
  return createStore<StoreLocatorStore>()((set) => ({
    isOpen: false,
    mode: 'input',
    selectedStoreInfo: init?.selectedStoreInfo ?? null,
    open: () => set({isOpen: true}),
    close: () => set({isOpen: false}),
    setSelectedStoreInfo: (selectedStoreInfo) => set({selectedStoreInfo}),
  }));
};

Context Integration

Expose app-level state to components via providers/hooks:

import { useBasket, useBasketSnapshot } from '@/providers/basket';

// In components
function CartIcon() {
    const basket = useBasket();
    const snapshot = useBasketSnapshot();

    const itemCount =
        basket?.productItems?.length ??
        snapshot?.uniqueProductCount ??
        0;

    return <Badge count={itemCount} />;
}

Post-Mutation Sync Pattern

Keep mutations on the server and update request-context resources there:

import {data} from 'react-router';
import {getBasket, updateBasketResource} from '@/middlewares/basket.server';

export async function action({request, context}: ActionFunctionArgs) {
  const formData = await request.formData();
  const productId = formData.get('productId') as string;

  const basketResource = await getBasket(context);
  const clients = createApiClients(context);

  const {data: updatedBasket} = await clients.basket.addItemToBasket({
    params: {path: {basketId: basketResource.current?.basketId ?? ''}},
    body: {productId, quantity: 1},
  });

  // Sync basket resource in request context for current response/revalidation flow
  updateBasketResource(context, updatedBasket);

  return data({success: true, basket: updatedBasket});
}

Best Practices

  1. Server-first data — Load/mutate commerce data with loader/action
  2. Provider-first app state — Use providers for shared basket/auth UI state
  3. Scoped Zustand usage — Use Zustand for feature-local complexity (typically extensions)
  4. Sync in server actions — Update server basket/auth resources inside action handlers
  5. Keep state minimal — Store only what cannot be derived cheaply

When to Use Each Mechanism

| Scenario | Use | | ----------------------------- | -------------------------------------------------------- | | Product data on page load | loader | | Shopping cart badge count | Basket provider hooks (useBasket, useBasketSnapshot) | | Complex extension UI workflow | Zustand store | | Search results | loader | | Add to cart | Server action + resource update |

Related Skills

  • storefront-next:sfnext-data-fetching - Server-side data loading with loaders (NOT client state)
  • storefront-next:sfnext-authentication - Auth state management
  • storefront-next:sfnext-components - Using store data in components