AgentSkillsCN

state-patterns

为 The Puppy Day 强制执行一致的 Zustand 存储模式与状态管理决策。在创建 Zustand 存储、管理客户端状态或决定状态管理方法时自动触发。

SKILL.md
--- frontmatter
name: state-patterns
description: Enforces consistent Zustand store patterns and state management decisions for The Puppy Day. Auto-invoke when creating Zustand stores, managing client-side state, or deciding between state management approaches.
metadata:
  author: thepuppyday
  version: "1.0.0"

State Management Patterns

Consistent Zustand store structure and state management decisions for The Puppy Day.

When to Apply

Reference these patterns when:

  • Creating new Zustand stores
  • Modifying existing stores
  • Deciding how to manage component state
  • Adding persistence to stores

Rule 1: When to Use What

State TypeSolutionExample
UI state (single component)useStateModal open/close, form inputs
UI state (few siblings)Props / lifting stateSelected tab shared between panels
URL-driven statesearchParams / URLFilters, pagination, active tab
Cross-component stateZustand storeBooking wizard, auth state
Server dataServer Component / fetchPage-level data, initial load
Server data + client updatesuseState with initial propsLists with client-side filtering

Default to the simplest option. Only use Zustand when state must be shared across unrelated components.

Rule 2: Store Structure

Separate State (data) and Actions (methods) interfaces:

typescript
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

// 1. State interface — data only
export interface BookingState {
  currentStep: number;
  selectedServiceId: string | null;
  selectedDate: string | null;
}

// 2. Actions interface — methods only
export interface BookingActions {
  setCurrentStep: (step: number) => void;
  selectService: (serviceId: string) => void;
  selectDate: (date: string) => void;
  reset: () => void;
}

// 3. Combined type
type BookingStore = BookingState & BookingActions;

// 4. Initial state (reusable for reset)
const initialState: BookingState = {
  currentStep: 0,
  selectedServiceId: null,
  selectedDate: null,
};

// 5. Store creation
export const useBookingStore = create<BookingStore>()(
  persist(
    (set) => ({
      ...initialState,

      setCurrentStep: (step) => set({ currentStep: step }),
      selectService: (serviceId) => set({ selectedServiceId: serviceId }),
      selectDate: (date) => set({ selectedDate: date }),
      reset: () => set(initialState),
    }),
    {
      name: 'booking-store',
      version: 1,
      storage: createJSONStorage(() => sessionStorage),
    }
  )
);

Rule 3: Action Naming Conventions

PrefixPurposeExample
setXSimple settersetCurrentStep(step)
selectXUser selection actionselectService(serviceId)
resetX / resetReset to initial statereset(), resetFilters()
fetchXAsync data loadingfetchAppointments()
addXAdd to collectionaddAddon(addon)
removeXRemove from collectionremoveAddon(addonId)
toggleXBoolean toggletoggleSidebar()
updateXPartial updateupdateCustomer(partial)

Rule 4: Persistence

StorageWhen to UseExample
sessionStorageEphemeral (dies with tab)Booking wizard state, form progress
localStoragePersistent across sessionsAdmin sidebar preference, theme
NoneTransient (dies with navigation)Loading states, temporary UI state

Always specify name and version:

typescript
persist(
  (set) => ({ /* ... */ }),
  {
    name: 'store-name',     // Unique key in storage
    version: 1,              // Bump on breaking changes
    storage: createJSONStorage(() => sessionStorage),
  }
)

Rule 5: Store File Naming

code
src/stores/
├── bookingStore.ts     // camelCase with 'Store' suffix
├── admin-store.ts      // kebab-case also acceptable
├── auth-store.ts       // Prefer one convention per project

Hook naming: useXStore (e.g., useBookingStore, useAdminStore).

Rule 6: Selectors for Performance

Use selectors to prevent unnecessary re-renders:

typescript
// CORRECT — only re-renders when currentStep changes
const currentStep = useBookingStore((state) => state.currentStep);

// WRONG — re-renders on ANY store change
const store = useBookingStore();
const currentStep = store.currentStep;

Audit Checklist

  • Separate State and Actions interfaces
  • initialState object defined for reset functionality
  • Action names follow the naming convention
  • Persist middleware specifies name and version
  • Components use selectors (not full store destructuring)
  • Store has a reset() action

Reference Files

  • src/stores/bookingStore.ts — Well-structured store (reference pattern)
  • src/stores/admin-store.ts — Admin panel state