--- name: frontend-react-best-practices description: React performance optimization guidelines. Use when writing, reviewing, or refactoring React components to ensure optimal rendering and bundle patterns. Triggers on tasks involving React components, hooks, memoization, or bundle optimization. --- # React Best Practices Performance optimization and composition patterns for React components. Contains 32 rules across 6 categories focused on reducing re-renders, optimizing bundles, component composition, and avoiding common React pitfalls. ## When to Apply Reference these guidelines when: - Writing new React components - Reviewing code for performance issues - Refactoring existing React code - Optimizing bundle size - Working with hooks and state ## Rules Summary ### Bundle Size Optimization (CRITICAL) #### bundle-barrel-imports - @rules/bundle-barrel-imports.md Import directly from source, avoid barrel files. ```tsx // Bad: loads entire library (200-800ms) import { Check, X } from "lucide-react"; // Good: loads only what you need import Check from "lucide-react/dist/esm/icons/check"; import X from "lucide-react/dist/esm/icons/x"; ``` #### bundle-conditional - @rules/bundle-conditional.md Load modules only when feature is activated. ```tsx useEffect(() => { if (enabled && typeof window !== "undefined") { import("./heavy-module").then((mod) => setModule(mod)); } }, [enabled]); ``` #### bundle-preload - @rules/bundle-preload.md Preload on hover/focus for perceived speed. ```tsx ``` ### Re-render Optimization (MEDIUM) #### rerender-functional-setstate - @rules/rerender-functional-setstate.md Use functional setState for stable callbacks. ```tsx // Bad: stale closure risk, recreates on items change const addItem = useCallback( (item) => { setItems([...items, item]); }, [items], ); // Good: always uses latest state, stable reference const addItem = useCallback((item) => { setItems((curr) => [...curr, item]); }, []); ``` #### rerender-derived-state-no-effect - @rules/rerender-derived-state-no-effect.md Derive state during render, not in effects. ```tsx // Bad: extra state and effect, extra render const [fullName, setFullName] = useState(""); useEffect(() => { setFullName(firstName + " " + lastName); }, [firstName, lastName]); // Good: derived directly during render const fullName = firstName + " " + lastName; ``` #### rerender-lazy-state-init - @rules/rerender-lazy-state-init.md Pass function to useState for expensive initial values. ```tsx // Bad: runs expensiveComputation() on every render const [data] = useState(expensiveComputation()); // Good: runs only on initial render const [data] = useState(() => expensiveComputation()); ``` #### rerender-dependencies - @rules/rerender-dependencies.md Use primitive dependencies in effects. ```tsx // Bad: runs on any user field change useEffect(() => { console.log(user.id); }, [user]); // Good: runs only when id changes useEffect(() => { console.log(user.id); }, [user.id]); ``` #### rerender-derived-state - @rules/rerender-derived-state.md Subscribe to derived booleans, not raw values. ```tsx // Bad: re-renders on every pixel change const width = useWindowWidth(); const isMobile = width < 768; // Good: re-renders only when boolean changes const isMobile = useMediaQuery("(max-width: 767px)"); ``` #### rerender-memo - @rules/rerender-memo.md Extract expensive work into memoized components. ```tsx // Good: skips computation when loading const UserAvatar = memo(function UserAvatar({ user }) { let id = useMemo(() => computeAvatarId(user), [user]); return ; }); function Profile({ user, loading }) { if (loading) return ; return ; } ``` #### rerender-memo-with-default-value - @rules/rerender-memo-with-default-value.md Hoist default non-primitive props to constants. ```tsx // Bad: breaks memoization (new function each render) const Button = memo(({ onClick = () => {} }) => ...) // Good: stable default value const NOOP = () => {} const Button = memo(({ onClick = NOOP }) => ...) ``` #### rerender-simple-expression-in-memo - @rules/rerender-simple-expression-in-memo.md Don't wrap simple primitive expressions in useMemo. ```tsx // Bad: useMemo overhead > expression cost const isLoading = useMemo(() => a.loading || b.loading, [a.loading, b.loading]); // Good: just compute it const isLoading = a.loading || b.loading; ``` #### rerender-move-effect-to-event - @rules/rerender-move-effect-to-event.md Put interaction logic in event handlers, not effects. ```tsx // Bad: effect re-runs on theme change useEffect(() => { if (submitted) post("/api/register"); }, [submitted, theme]); // Good: in handler const handleSubmit = () => post("/api/register"); ``` #### rerender-transitions - @rules/rerender-transitions.md Use startTransition for non-urgent updates. ```tsx // Good: non-blocking scroll tracking const handler = () => { startTransition(() => setScrollY(window.scrollY)); }; ``` #### rerender-use-ref-transient-values - @rules/rerender-use-ref-transient-values.md Use refs for transient frequent values. ```tsx // Good: no re-render, direct DOM update const lastXRef = useRef(0); const dotRef = useRef(null); useEffect(() => { let onMove = (e) => { lastXRef.current = e.clientX; dotRef.current?.style.transform = `translateX(${e.clientX}px)`; }; window.addEventListener("mousemove", onMove); return () => window.removeEventListener("mousemove", onMove); }, []); ``` ### Rendering Performance (MEDIUM) #### rendering-conditional-render - @rules/rendering-conditional-render.md Use ternary, not && for conditionals with numbers. ```tsx // Bad: renders "0" when count is 0 { count && {count}; } // Good: renders nothing when count is 0 { count > 0 ? {count} : null; } ``` #### rendering-hoist-jsx - @rules/rendering-hoist-jsx.md Extract static JSX outside components. ```tsx // Good: reuses same element, especially for large SVGs const skeleton =
; function Container({ loading }) { return loading ? skeleton : ; } ``` #### rendering-content-visibility - @rules/rendering-content-visibility.md Use content-visibility for long lists. ```css .list-item { content-visibility: auto; contain-intrinsic-size: 0 80px; } ``` #### rendering-animate-svg-wrapper - @rules/rendering-animate-svg-wrapper.md Animate wrapper div, not SVG element (for GPU acceleration). ```tsx // Good: hardware accelerated
...
``` #### rendering-svg-precision - @rules/rendering-svg-precision.md Reduce SVG coordinate precision with SVGO. ```bash npx svgo --precision=1 --multipass icon.svg ``` #### rendering-hydration-no-flicker - @rules/rendering-hydration-no-flicker.md Use inline script for client-only data to prevent flicker. ```tsx
{children}