Skip to main content

frontend

Use when designing React component structure, deciding where state should live, implementing data fetching with TanStack Query, building validated forms, or optimizing rendering performance.

Informations de source

Dépôt
kid-sid/codex-spellbook
Dernière activité de la source
11 mai 2026 à 18:04
Langue détectée de SKILL.md
anglais
Étoiles
22
Forks
1

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
frontend
description
Use when designing React component structure, deciding where state should live, implementing data fetching with TanStack Query, building validated forms, or optimizing rendering performance.
# Frontend Patterns Conventions and best practices for building maintainable, performant React applications. ## When to Activate - Designing component structure or deciding where state should live - Choosing between local state, Context, Zustand, or Redux Toolkit - Implementing data fetching, caching, or server state synchronization - Building forms with validation and submission handling - Optimizing rendering performance (re-renders, bundle size, lazy loading) - Setting up routing with protected routes or nested layouts - Writing tests for React components and hooks ## Component Design ### Single Responsibility ```tsx // BAD: component does too many things function UserDashboard({ userId }: { userId: string }) { const [user, setUser] = useState(null); const [orders, setOrders] = useState([]); useEffect(() => { /* fetch user + orders */ }, []); return <div>{/* render user, orders, sidebar, notifications */}</div>; } // GOOD: split by concern function UserDashboard({ userId }: { userId: string }) { return ( <DashboardLayout> <UserProfile userId={userId} /> <OrderList userId={userId} /> <NotificationPanel userId={userId} /> </DashboardLayout> ); } ``` ### Composition over Props Drilling ```tsx // BAD: prop drilling through 3+ levels <Page user={user} onLogout={onLogout} theme={theme} /> <Header user={user} onLogout={onLogout} theme={theme} /> <Nav user={user} onLogout={onLogout} theme={theme} /> // GOOD: compound component or slot pattern <Page> <Header> <Nav /> <UserMenu /> </Header> <main>{children}</main> </Page> ``` ### Container / Presentational Split ```tsx // Container: owns data fetching and state function OrderListContainer({ userId }: { userId: string }) { const { data, isLoading, error } = useOrders(userId); if (isLoading) return <OrderListSkeleton />; if (error) return <ErrorBoundary error={error} />; return <OrderList orders={data} />; } // Presentational: pure rendering, easily testable function OrderList({ orders }: { orders: Order[] }) { return ( <ul> {orders.map(o => <OrderItem key={o.id} order={o} />)} </ul> ); } ``` ## State Management ### Decision Matrix | State Type | Tool | Examples | |---|---|---| | UI / ephemeral | `useState` / `useReducer` | modal open, input value, toggle | | Shared UI state | Zustand or Context | theme, sidebar collapsed, selected tab | | Server state | TanStack Query | API responses, lists, detail pages | | Complex client state | Redux Toolkit | shopping cart, multi-step wizard, offline queue | | URL state | Search params | filters, pagination, selected item | | Form state | React Hook Form | field values, validation, submission | ### Zustand (preferred for shared UI state) ```tsx import { create } from "zustand"; interface SidebarStore { isOpen: boolean; toggle: () => void; open: () => void; close: () => void; } const useSidebarStore = create<SidebarStore>((set) => ({ isOpen: false, toggle: () => set((s) => ({ isOpen: !s.isOpen })), open: () => set({ isOpen: true }), close: () => set({ isOpen: false }), })); // With persistence import { persist } from "zustand/middleware"; const useThemeStore = create(persist( (set) => ({ theme: "light", setTheme: (t) => set({ theme: t }) }), { name: "theme-storage" }, )); ``` ### Redux Toolkit (complex client state) ```tsx import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; const fetchOrders = createAsyncThunk("orders/fetch", async (userId: string) => { const res = await fetch(`/api/users/${userId}/orders`); return res.json(); }); const ordersSlice = createSlice({ name: "orders", initialState: { items: [] as Order[], status: "idle" as LoadingStatus }, reducers: { removeOrder: (state, action) => { state.items = state.items.filter(o => o.id !== action.payload); }, }, extraReducers: (builder) => { builder .addCase(fetchOrders.pending, (state) => { state.status = "loading"; }) .addCase(fetchOrders.fulfilled, (state, action) => { state.status = "succeeded"; state.items = action.payload; }) .addCase(fetchOrders.rejected, (state) => { state.status = "failed"; }); }, }); ``` ## Data Fetching ### TanStack Query (server state) ```tsx import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; // Fetching function useOrders(userId: string) { return useQuery({ queryKey: ["orders", userId], queryFn: () => fetchOrders(userId), staleTime: 5 * 60 * 1000, // 5 min — don't refetch if fresh gcTime: 10 * 60 * 1000, // 10 min — keep in cache after unmount }); } // Mutation with optimistic update function useCancelOrder() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (orderId: string) => cancelOrder(orderId), onMutate: async (orderId) => { await queryClient.cancelQueries({ queryKey: ["orders"] }); const prev = queryClient.getQueryData(["orders"]); queryClient.setQueryData(["orders"], (old: Order[]) => old.filter(o => o.id !== orderId), ); return { prev }; }, onError: (_, __, ctx) => { queryClient.setQueryData(["orders"], ctx?.prev); }, onSettled: () => queryClient.invalidateQueries({ queryKey: ["orders"] }), }); } ``` ### Query Key Conventions ```tsx // Hierarchical keys enable targeted invalidation ["users"] // all users ["users", userId] // one user ["users", userId, "orders"] // user's orders ["users", userId, "orders", { status: "active" }] // filtered queryClient.invalidateQueries({ queryKey: ["users", userId] }); // ^ invalidates user + all sub-keys (orders, profile, etc.) ``` ## Forms ### React Hook Form + Zod ```tsx import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; const schema = z.object({ email: z.string().email("Invalid email"), password: z.string().min(8, "At least 8 characters"), role: z.enum(["admin", "user"]), }); type FormValues = z.infer<typeof schema>; function CreateUserForm() { const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormValues>({ resolver: zodResolver(schema) }); const onSubmit = async (data: FormValues) => { await createUser(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("email")} /> {errors.email && <p>{errors.email.message}</p>} <input type="password" {...register("password")} /> {errors.password && <p>{errors.password.message}</p>} <button type="submit" disabled={isSubmitting}> {isSubmitting ? "Creating…" : "Create User"} </button> </form> ); } ``` ## Routing (React Router v6) ### Nested Layouts with Protected Routes ```tsx import { createBrowserRouter, RouterProvider, Outlet, Navigate } from "react-router-dom"; function RequireAuth() { const { user } = useAuth(); return user ? <Outlet /> : <Navigate to="/login" replace />; } const router = createBrowserRouter([ { path: "/login", element: <LoginPage /> }, { element: <RequireAuth />, children: [ { element: <AppShell />, // persistent layout (nav, sidebar) children: [ { path: "/", element: <Dashboard /> }, { path: "/orders", element: <OrderList /> }, { path: "/orders/:id", element: <OrderDetail />, loader: ({ params }) => fetchOrder(params.id!) }, ], }, ], }, ]); ``` ### URL State for Filters ```tsx import { useSearchParams } from "react-router-dom"; function OrderFilters() { const [params, setParams] = useSearchParams(); const status = params.get("status") ?? "all"; return ( <select value={status} onChange={e => setParams(p => { p.set("status", e.target.value); return p; }) }> <option value="all">All</option> <option value="active">Active</option> </select> ); } ``` ## Performance ### Memoization — when it helps ```tsx // useMemo: expensive derivation, stable reference for children const sortedOrders = useMemo( () => [...orders].sort((a, b) => b.total - a.total), [orders], ); // useCallback: stable reference passed to memo'd child or as dep const handleCancel = useCallback((id: string) => cancelOrder(id), [cancelOrder]); // memo: skip re-render when props unchanged const OrderItem = memo(function OrderItem({ order }: { order: Order }) { return <li>{order.id}</li>; }); // BAD: wrapping everything — memo adds overhead, only helps when re-renders are expensive ``` ### Code Splitting ```tsx import { lazy, Suspense } from "react"; // Route-level splitting (most impactful) const Dashboard = lazy(() => import("./pages/Dashboard")); const Reports = lazy(() => import("./pages/Reports")); function App() { return ( <Suspense fallback={<PageSkeleton />}> <Routes> <Route path="/" element={<Dashboard />} /> <Route path="/reports" element={<Reports />} /> </Routes> </Suspense> ); } ``` ### Virtualization for Long Lists ```tsx import { useVirtualizer } from "@tanstack/react-virtual"; function VirtualOrderList({ orders }: { orders: Order[] }) { const parentRef = useRef<HTMLDivElement>(null); const virtualizer = useVirtualizer({ count: orders.length, getScrollElement: () => parentRef.current, estimateSize: () => 64, }); return ( <div ref={parentRef} style={{ height: "600px", overflow: "auto" }}> <div style={{ height: virtualizer.getTotalSize() }}> {virtualizer.getVirtualItems().map(item => (
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub