AgentSkillsCN

frontend-react

在编辑 .tsx、.jsx 文件,或在 components/、pages/、store/、hooks/ 中工作时加载。提供 React、TypeScript、Zustand 状态管理,以及 WebSocket 客户端模式。

SKILL.md
--- frontmatter
name: frontend-react
description: Load when editing .tsx, .jsx files or working in components/, pages/, store/, hooks/. Provides React, TypeScript, Zustand state management, and WebSocket client patterns.

Frontend React

Merged Skills

  • state-management: Zustand stores, selectors, subscriptions
  • internationalization: i18n, translation, locale patterns
  • performance: React.memo, useMemo, useCallback optimization
  • authentication: Login flows, token storage, auth guards
  • websocket-realtime: WebSocket client, reconnection, message handling

⚠️ Critical Gotchas

CategoryPatternSolution
Auth401 errorsCall logout() from authStore, don't show page-level error
JSXComment syntax errorUse {/* comment */} not //
HooksStale closuresAdd all deps to useEffect dependency array
StateSettings lost on refreshUse localStorage for persistent settings
ZustandMemory leaksClean up selectors/subscriptions
AuthWrong storage keyUse localStorage.getItem('nop-auth') not 'token'
AsyncState stale in callbackCapture with { ...localState } BEFORE async calls
ConfigPanelSave lostCall saveCurrentWorkflow() after updateNode()

Rules

RulePattern
Keys in listsAlways key={item.id}
Dependency arraysInclude all dependencies
Async in effectsUse wrapper function, never async callback
State managementZustand for global, useState for local
Auth handlingRedirect on 401, don't show error page

Avoid

❌ Bad✅ Good
Prop drillingContext or Zustand
useEffect(async () => ...)Wrapper function inside
Missing keyskey={id}
Page-level 401 UIlogout() redirect
// comment in JSX{/* comment */}

Patterns

tsx
// Pattern 1: Component with Zustand selector
const items = useStore((s) => s.items);
const Card: FC<{item: Item}> = ({ item }) => (
  <div key={item.id}>{item.name}</div>
);

// Pattern 2: Store with persistence
export const useStore = create<State>()(
  persist(
    (set) => ({
      items: [],
      addItem: (i) => set((s) => ({ items: [...s.items, i] }))
    }),
    { name: 'store-key' }
  )
);

// Pattern 3: Async state capture (CRITICAL)
const handleSave = async () => {
  const capturedParams = { ...localParams };  // Capture BEFORE async
  await updateNode(nodeId, { data: { ...node.data, ...capturedParams }});
  await saveCurrentWorkflow();  // Persist to backend
};

// Pattern 4: Execution visualization in BlockNode
const executionStatus = (data as any).executionStatus as NodeExecutionStatus;
const borderColor = executionStatus ? statusColors[executionStatus] : categoryColor;
const isExecuting = executionStatus === 'running';

// Pattern 5: Auth-aware API call
const fetchData = async () => {
  try {
    const response = await api.get('/resource');
    return response.data;
  } catch (error) {
    if (error.response?.status === 401) {
      logout();  // Redirect, don't show error
      return;
    }
    throw error;
  }
};

Execution Visualization

StatusBorder ColorEffect
runningcyananimate-pulse + glow
completedgreenstatic glow
failedredstatic glow
pendinggraydefault

Commands

TaskCommand
Start devcd frontend && npm start
Run testscd frontend && npm test
Buildcd frontend && npm run build
Type checkcd frontend && npx tsc --noEmit
Lintcd frontend && npm run lint