Transform hub-guide from a single-skill Hub monorepo guide into a comprehensive programming best-practice plugin covering all situations. Skills (26): - Core: engineering-principles, clean-code, clean-architecture, design-patterns, testing, error-handling, security, api-design, git-workflow, documentation, logging-observability, performance - Languages: typescript, python, rust, go - Frameworks: react-frontend, elysiajs, hono-backend, drizzle-database, nextjs - Infrastructure: docker, ci-cd, monitoring - Monorepo: monorepo, hub-guide (existing) Hooks: - SessionStart: auto-detect project type and activate relevant skills - PreToolUse (Write|Edit): inject language-specific rules per file type Reference files for deep dives: - clean-architecture/references/solid.md (SOLID + component principles) - design-patterns/references/catalog.md (full GoF catalog with examples) - testing/references/mocks.md (test double taxonomy) Restructure plugin to modern skills/ directory format.
6.6 KiB
6.6 KiB
name, description
| name | description |
|---|---|
| react-frontend | React and frontend best practices — component patterns, hooks, state management, TanStack Query, React Router, performance, and testing. Use when building React components, designing state management, or whenever the user mentions "React," "hooks," "state management," "component," "JSX," "TanStack Query," "React Router," "Zustand," "Vite," "Next.js," or "Frontend." |
React Frontend Best Practices
Component Patterns
Composition over Inheritance
// ✅ Prefer composition
function Layout({ sidebar, children }: { sidebar: ReactNode; children: ReactNode }) {
return <div className="layout">{sidebar}<main>{children}</main></div>;
}
// ❌ Avoid inheritance patterns in React
Container/Presentational Separation
// Container: manages state, data fetching, business logic
function UserProfileContainer() {
const { data: user } = useUserQuery(userId);
const { mutate: update } = useUpdateUserMutation();
return <UserProfile user={user!} onUpdate={update} />;
}
// Presentational: pure rendering, props-only
function UserProfile({ user, onUpdate }: UserProfileProps) {
return <div>{user.name} <button onClick={onUpdate}>Edit</button></div>;
}
Custom Hooks for Logic Extraction
// Extract reusable logic into custom hooks
function useUserPermissions(userId: string) {
const { data: user } = useUserQuery(userId);
return useMemo(() => ({
isAdmin: user?.role === 'admin',
canEdit: user?.role === 'admin' || user?.role === 'editor',
canDelete: user?.role === 'admin',
}), [user]);
}
Hooks Rules
- Only call hooks at the top level — not in conditions, loops, or callbacks.
- Only call hooks from React functions — component or custom hook.
- Deps array matches reality — include all values used inside.
useMemofor expensive computations.useCallbackfor stable references.useEffectis for synchronization, not lifecycle. If you can compute from state, do it.
// ❌ Unnecessary effect
const [fullName, setFullName] = useState('');
useEffect(() => { setFullName(`${first} ${last}`); }, [first, last]);
// ✅ Derived state
const fullName = `${first} ${last}`;
Data Fetching (TanStack Query)
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// Query
function useUserQuery(id: string) {
return useQuery({
queryKey: ['users', id],
queryFn: () => api.users.get({ params: { id } }),
staleTime: 30_000, // 30s before refetch
gcTime: 5 * 60_000, // 5min cache
});
}
// Mutation with optimistic update
function useUpdateUserMutation() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: UpdateUserInput) => api.users.update({ body: data }),
onMutate: async (newUser) => {
await queryClient.cancelQueries({ queryKey: ['users', newUser.id] });
const previous = queryClient.getQueryData(['users', newUser.id]);
queryClient.setQueryData(['users', newUser.id], newUser);
return { previous };
},
onError: (_, __, context) => {
queryClient.setQueryData(['users', context.previous.id], context.previous);
},
onSettled: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
});
}
Rules:
staleTimefor read-through caching. Default 0 = refetch on mount.gcTimefor garbage collection of unused data.onMutate/onError/onSettledfor optimistic updates.- Queries over custom fetch + useEffect in all cases.
State Management Selection
| Need | Solution |
|---|---|
| Server state | TanStack Query |
| URL state | React Router / TanStack Router |
| Form state | React Hook Form + Zod |
| Client state (global) | Zustand or Context |
| Client state (local) | useState / useReducer |
| Component communication | Props / lifting state up |
// Zustand — lightweight, no boilerplate
import { create } from 'zustand';
interface UIStore {
sidebarOpen: boolean;
toggleSidebar: () => void;
}
const useUIStore = create<UIStore>((set) => ({
sidebarOpen: true,
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}));
Routing (TanStack Router / React Router)
// TanStack Router — type-safe, modern
const router = createRouter({
routeTree: rootRoute.addChildren([
indexRoute,
usersRoute.addChildren([userRoute, userProfileRoute]),
]),
});
- File-based routing (Next.js App Router, Vite/Router) for simpler projects.
- Type-safe routers (TanStack Router) for larger apps.
- Lazy load route components —
React.lazy(() => import('./routes/Dashboard')).
Performance
- Virtual lists —
@tanstack/react-virtualfor 100+ items. - React.memo sparingly — only for components that re-render often with same props.
useMemofor expensive calculations — not for every value.- Code splitting — per route, per heavy component.
- Bundle analysis —
vite-bundle-visualizerto find bloat.
Testing (Vitest + Testing Library)
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserProfile } from './UserProfile';
describe('UserProfile', () => {
it('renders user name', () => {
render(<UserProfile user={{ name: 'Alice' }} />);
expect(screen.getByText('Alice')).toBeInTheDocument();
});
it('calls onUpdate when edit clicked', async () => {
const onUpdate = vi.fn();
render(<UserProfile user={{ name: 'Alice' }} onUpdate={onUpdate} />);
await userEvent.click(screen.getByRole('button', { name: /edit/i }));
expect(onUpdate).toHaveBeenCalledTimes(1);
});
});
- Testing Library for user-centric tests. Never test implementation details.
- userEvent over
fireEvent— simulates real user interactions. - Component-level tests for behavior, not storybook-style visual tests here.
CSS / Styling
- Tailwind CSS for utility-first styling. Consistent, fast, small.
- CSS Modules when you need scoped component styles.
- CSS-in-JS (styled-components, emotion) — only for dynamic theming. Prefer Tailwind.
Anti-patterns
- ❌
useEffectfor data fetching — use TanStack Query - ❌ Prop drilling beyond 3 levels — compose or context
- ❌
useStatefor derived data — compute from existing state - ❌ Direct DOM manipulation — use React refs
- ❌
anyin component props — always type props - ❌ Large component files — split by responsibility
- ❌
indexas key — breaks reconciliation on reorder - ❌
useEffectwithout deps — runs every render