Expert knowledge in React patterns, component design, state management, and frontend best practices. Use for frontend development tasks.
Modern React patterns and best practices for building scalable applications.
// 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>
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>
)}
/>
// 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;
}
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);
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);
// 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() });
},
});
};
// 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>
));
// 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>
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>
);
}
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;
}
}
// 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;
}
};
npx skills add claudeforge/frontend-patterns下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer