frontend-patterns
SKILL.md
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>
2. 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>
)}
/>
3. 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;
}
4. Higher-Order Components (HOC)
function withAuth<P extends object>(Component: ComponentType<P>) {
return function AuthenticatedComponent(props: P) {
const { user, isLoading } = useAuth();
if (isLoading) return <Spinner />;
if (!user) return <Navigate to="/login" />;
return <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;
}
};
Weekly Installs
3
Repository
claudeforge/orchestratorGitHub Stars
36
First Seen
12 days ago
Security Audits
Installed on
opencode3
gemini-cli3
github-copilot3
codex3
amp3
cline3