Frontend patterns
Skill tranhieutt/software_development_department/.claude/skills/frontend-patterns
Software Development Department
npx -y skills add tranhieutt/software_development_department --skill frontend-patternsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Framework-agnostic React/Vue patterns — component composition, hooks, TanStack Query, memoization, error boundaries. Use for generic React/Vue work (Vite, CRA, Storybook). For Next.js App Router / Server Components specifically, use `senior-frontend` instead.
SKILL.md
5.4 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it
Frontend Patterns
Critical rules (non-obvious)
- Stale closure in
useEffect: always list all dependencies; useuseReffor values that shouldn't trigger re-run useEffectwithasync: never make the callbackasyncdirectly — create inner async fn and call it- Object/array as dependency: memoize with
useMemo/useCallbackor use primitive values; otherwise infinite loop - Key prop on lists: use stable IDs, never
indexwhen list can reorder or items get deleted React.memois not free: only wrap components with expensive renders and stable prop references
Component composition patterns
// Compound component with Context
const TabsContext = createContext<{ active: string; setActive: (v: string) => void } | null>(null);
function Tabs({ children, defaultValue }: { children: React.ReactNode; defaultValue: string }) {
const [active, setActive] = useState(defaultValue);
return <TabsContext.Provider value={{ active, setActive }}>{children}</TabsContext.Provider>;
}
Tabs.Trigger = function TabsTrigger({ value, children }: { value: string; children: React.ReactNode }) {
const ctx = useContext(TabsContext)!;
return <button onClick={() => ctx.setActive(value)} aria-selected={ctx.active === value}>{children}</button>;
};
Tabs.Content = function TabsContent({ value, children }: { value: string; children: React.ReactNode }) {
const { active } = useContext(TabsContext)!;
return active === value ? <>{children}</> : null;
};
State management decision
| Scope | Solution |
|---|---|
| Single component | useState, useReducer |
| Subtree | Context + useContext |
| Client global (UI) | Zustand / Jotai |
| Server state (API) | TanStack Query |
| Form state | React Hook Form |
| URL state | useSearchParams (Next.js) |
Data fetching with TanStack Query
// Fetch
const { data, isLoading, error } = useQuery({
queryKey: ["products", filters], // filters in key → auto-refetch on change
queryFn: () => api.getProducts(filters),
staleTime: 5 * 60 * 1000, // don't refetch for 5 min
});
// Mutate with optimistic update
const mutation = useMutation({
mutationFn: api.updateProduct,
onMutate: async (newProduct) => {
await queryClient.cancelQueries({ queryKey: ["products"] });
const prev = queryClient.getQueryData(["products"]);
queryClient.setQueryData(["products"], (old) => old.map(p => p.id === newProduct.id ? newProduct : p));
return { prev };
},
onError: (_, __, ctx) => queryClient.setQueryData(["products"], ctx?.prev),
onSettled: () => queryClient.invalidateQueries({ queryKey: ["products"] }),
});
Performance: avoid re-renders
// Memoize expensive component
const ExpensiveList = memo(({ items }: { items: Item[] }) => (
<ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>
));
// Stable callback reference
const handleClick = useCallback((id: string) => {
onSelect(id);
}, [onSelect]); // only recreate if onSelect changes
// Expensive calculation
const sorted = useMemo(() =>
items.sort((a, b) => b.score - a.score),
[items]);
Code splitting
const HeavyChart = lazy(() => import("./HeavyChart"));
function Dashboard() {
return (
<Suspense fallback={<Skeleton />}>
<HeavyChart data={data} />
</Suspense>
);
}
Custom hooks pattern
function useDebounce<T>(value: T, delay: number): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
function useLocalStorage<T>(key: string, initial: T) {
const [value, setValue] = useState<T>(() => {
try { return JSON.parse(localStorage.getItem(key) ?? "") ?? initial; }
catch { return initial; }
});
const set = useCallback((v: T) => {
setValue(v); localStorage.setItem(key, JSON.stringify(v));
}, [key]);
return [value, set] as const;
}
Error boundaries
class ErrorBoundary extends React.Component<{ fallback: React.ReactNode; children: React.ReactNode }> {
state = { hasError: false };
static getDerivedStateFromError() { return { hasError: true }; }
componentDidCatch(error: Error) { console.error(error); }
render() { return this.state.hasError ? this.props.fallback : this.props.children; }
}
// Usage: wrap async/complex sections, not entire app
Common pitfalls
| Pitfall | Fix |
|---|---|
Fetching in useEffect without cleanup | Use TanStack Query or abort controller |
| Context causes full tree re-render | Split context by domain; memoize value object |
useEffect runs twice (StrictMode) | Design effects to be idempotent; use cleanup fn |
| Prop drilling > 3 levels | Lift to Context or state manager |
Missing loading / error states | Always handle all 3 states: loading, error, data |
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.