frontend-patterns

Frontend Patterns Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "frontend-patterns" with this command: npx skills add claudeforge/orchestrator/claudeforge-orchestrator-frontend-patterns

Frontend Patterns Skill

Modern React patterns and best practices for building scalable applications.

Component Patterns

  1. Compound Components

// Parent provides context, children consume it const Tabs = ({ children, defaultTab }) => { const [activeTab, setActiveTab] = useState(defaultTab);

return ( <TabsContext.Provider value={{ activeTab, setActiveTab }}> <div className="tabs">{children}</div> </TabsContext.Provider> ); };

Tabs.List = ({ children }) => <div className="tabs-list">{children}</div>; Tabs.Tab = ({ id, children }) => { const { activeTab, setActiveTab } = useTabsContext(); return ( <button className={activeTab === id ? 'active' : ''} onClick={() => setActiveTab(id)} > {children} </button> ); }; Tabs.Panel = ({ id, children }) => { const { activeTab } = useTabsContext(); return activeTab === id ? <div>{children}</div> : null; };

// Usage <Tabs defaultTab="tab1"> <Tabs.List> <Tabs.Tab id="tab1">Tab 1</Tabs.Tab> <Tabs.Tab id="tab2">Tab 2</Tabs.Tab> </Tabs.List> <Tabs.Panel id="tab1">Content 1</Tabs.Panel> <Tabs.Panel id="tab2">Content 2</Tabs.Panel> </Tabs>

  1. Render Props

interface MousePosition { x: number; y: number; }

const MouseTracker = ({ render, }: { render: (pos: MousePosition) => ReactNode; }) => { const [position, setPosition] = useState({ x: 0, y: 0 });

useEffect(() => { const handleMove = (e: MouseEvent) => { setPosition({ x: e.clientX, y: e.clientY }); }; window.addEventListener('mousemove', handleMove); return () => window.removeEventListener('mousemove', handleMove); }, []);

return <>{render(position)}</>; };

// Usage <MouseTracker render={({ x, y }) => ( <div>Mouse: {x}, {y}</div> )} />

  1. Custom Hooks

// Reusable logic extraction function useLocalStorage<T>(key: string, initialValue: T) { const [storedValue, setStoredValue] = useState<T>(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch { return initialValue; } });

const setValue = (value: T | ((val: T) => T)) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); } catch (error) { console.error(error); } };

return [storedValue, setValue] as const; }

  1. Higher-Order Components (HOC)

function withAuth<P extends object>(Component: ComponentType<P>) { return function AuthenticatedComponent(props: P) { const { user, isLoading } = useAuth();

if (isLoading) return &#x3C;Spinner />;
if (!user) return &#x3C;Navigate to="/login" />;

return &#x3C;Component {...props} />;

}; }

// Usage const ProtectedDashboard = withAuth(Dashboard);

State Management Patterns

Zustand Store Pattern

interface Store { // State items: Item[]; isLoading: boolean;

// Actions fetchItems: () => Promise<void>; addItem: (item: Item) => void; removeItem: (id: string) => void; }

const useStore = create<Store>((set, get) => ({ items: [], isLoading: false,

fetchItems: async () => { set({ isLoading: true }); try { const items = await api.getItems(); set({ items, isLoading: false }); } catch { set({ isLoading: false }); } },

addItem: (item) => set((state) => ({ items: [...state.items, item], })),

removeItem: (id) => set((state) => ({ items: state.items.filter((i) => i.id !== id), })), }));

// Selectors for performance const useItems = () => useStore((s) => s.items); const useIsLoading = () => useStore((s) => s.isLoading);

React Query Pattern

// Query keys factory const todoKeys = { all: ['todos'] as const, lists: () => [...todoKeys.all, 'list'] as const, list: (filters: string) => [...todoKeys.lists(), { filters }] as const, details: () => [...todoKeys.all, 'detail'] as const, detail: (id: string) => [...todoKeys.details(), id] as const, };

// Queries export const useTodos = (filters?: string) => useQuery({ queryKey: todoKeys.list(filters ?? ''), queryFn: () => fetchTodos(filters), });

// Mutations with optimistic updates export const useUpdateTodo = () => { const queryClient = useQueryClient();

return useMutation({ mutationFn: updateTodo, onMutate: async (newTodo) => { await queryClient.cancelQueries({ queryKey: todoKeys.lists() }); const previous = queryClient.getQueryData(todoKeys.lists()); queryClient.setQueryData(todoKeys.lists(), (old) => old?.map((t) => (t.id === newTodo.id ? newTodo : t)) ); return { previous }; }, onError: (err, newTodo, context) => { queryClient.setQueryData(todoKeys.lists(), context?.previous); }, onSettled: () => { queryClient.invalidateQueries({ queryKey: todoKeys.lists() }); }, }); };

Performance Patterns

Memoization

// Memoize expensive calculations const expensiveValue = useMemo(() => { return items.filter(x => x.active).reduce((acc, x) => acc + x.value, 0); }, [items]);

// Memoize callbacks const handleClick = useCallback((id: string) => { setSelected(id); }, []);

// Memoize components const MemoizedList = memo(({ items }: { items: Item[] }) => ( <ul> {items.map(item => <li key={item.id}>{item.name}</li>)} </ul> ));

Code Splitting

// Lazy load routes const Dashboard = lazy(() => import('./pages/Dashboard')); const Settings = lazy(() => import('./pages/Settings'));

// With loading fallback <Suspense fallback={<PageSkeleton />}> <Routes> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/settings" element={<Settings />} /> </Routes> </Suspense>

Virtual Lists

import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }: { items: Item[] }) { const parentRef = useRef<HTMLDivElement>(null);

const virtualizer = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, estimateSize: () => 50, });

return ( <div ref={parentRef} style={{ height: 400, overflow: 'auto' }}> <div style={{ height: virtualizer.getTotalSize() }}> {virtualizer.getVirtualItems().map((virtualItem) => ( <div key={virtualItem.key} style={{ position: 'absolute', top: virtualItem.start, height: virtualItem.size, }} > {items[virtualItem.index].name} </div> ))} </div> </div> ); }

Error Handling

Error Boundaries

class ErrorBoundary extends Component<Props, State> { state = { hasError: false, error: null };

static getDerivedStateFromError(error: Error) { return { hasError: true, error }; }

componentDidCatch(error: Error, info: ErrorInfo) { console.error('Error caught:', error, info); }

render() { if (this.state.hasError) { return <ErrorFallback error={this.state.error} />; } return this.props.children; } }

Accessibility Patterns

// Focus management const modalRef = useRef<HTMLDivElement>(null); useEffect(() => { modalRef.current?.focus(); }, []);

// ARIA attributes <button aria-label="Close modal" aria-expanded={isOpen} aria-controls="modal-content"

<CloseIcon /> </button>

// Keyboard navigation const handleKeyDown = (e: KeyboardEvent) => { switch (e.key) { case 'ArrowDown': setFocusedIndex(i => Math.min(i + 1, items.length - 1)); break; case 'ArrowUp': setFocusedIndex(i => Math.max(i - 1, 0)); break; case 'Enter': handleSelect(focusedIndex); break; } };

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

error-recovery

No summary provided by upstream source.

Repository SourceNeeds Review
General

quality-gates

No summary provided by upstream source.

Repository SourceNeeds Review
General

backend-patterns

No summary provided by upstream source.

Repository SourceNeeds Review