Claude Agent Skill · by Sergiodxa

Frontend React Best Practices

Install Frontend React Best Practices skill for Claude Code from sergiodxa/agent-skills.

Install
Terminal · npx
$npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-react-best-practices
Works with Paperclip

How Frontend React Best Practices fits into a Paperclip company.

Frontend React Best Practices drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md539 lines
Expand
---name: frontend-react-best-practicesdescription: 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 33 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 needimport 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. ```tsxuseEffect(() => {  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<button  onMouseEnter={() => import("./editor")}  onFocus={() => import("./editor")}  onClick={openEditor}>  Open Editor</button>``` ### 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 changeconst addItem = useCallback(  (item) => {    setItems([...items, item]);  },  [items],); // Good: always uses latest state, stable referenceconst 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 renderconst [fullName, setFullName] = useState("");useEffect(() => {  setFullName(firstName + " " + lastName);}, [firstName, lastName]); // Good: derived directly during renderconst 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 renderconst [data] = useState(expensiveComputation()); // Good: runs only on initial renderconst [data] = useState(() => expensiveComputation());``` #### rerender-dependencies - @rules/rerender-dependencies.md Use primitive dependencies in effects. ```tsx// Bad: runs on any user field changeuseEffect(() => {  console.log(user.id);}, [user]); // Good: runs only when id changesuseEffect(() => {  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 changeconst width = useWindowWidth();const isMobile = width < 768; // Good: re-renders only when boolean changesconst isMobile = useMediaQuery("(max-width: 767px)");``` #### rerender-memo - @rules/rerender-memo.md Extract expensive work into memoized components. ```tsx// Good: skips computation when loadingconst UserAvatar = memo(function UserAvatar({ user }) {  let id = useMemo(() => computeAvatarId(user), [user]);  return <Avatar id={id} />;}); function Profile({ user, loading }) {  if (loading) return <Skeleton />;  return <UserAvatar user={user} />;}``` #### 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 valueconst 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 costconst isLoading = useMemo(() => a.loading || b.loading, [a.loading, b.loading]); // Good: just compute itconst 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 changeuseEffect(() => {  if (submitted) post("/api/register");}, [submitted, theme]); // Good: in handlerconst handleSubmit = () => post("/api/register");``` #### rerender-transitions - @rules/rerender-transitions.md Use startTransition for non-urgent updates. ```tsx// Good: non-blocking scroll trackingconst 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 updateconst lastXRef = useRef(0);const dotRef = useRef<HTMLDivElement>(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 && <Badge>{count}</Badge>;} // Good: renders nothing when count is 0{  count > 0 ? <Badge>{count}</Badge> : null;}``` #### rendering-hoist-jsx - @rules/rendering-hoist-jsx.md Extract static JSX outside components. ```tsx// Good: reuses same element, especially for large SVGsconst skeleton = <div className="animate-pulse h-20 bg-gray-200" />; function Container({ loading }) {  return loading ? skeleton : <Content />;}``` #### 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<div className="animate-spin">  <svg>...</svg></div>``` #### rendering-svg-precision - @rules/rendering-svg-precision.md Reduce SVG coordinate precision with SVGO. ```bashnpx 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<div id="theme-wrapper">{children}</div><script dangerouslySetInnerHTML={{ __html: `  var theme = localStorage.getItem('theme') || 'light';  document.getElementById('theme-wrapper').className = theme;` }} />``` #### rendering-hydration-suppress-warning - @rules/rendering-hydration-suppress-warning.md Suppress expected hydration mismatches. ```tsx<span suppressHydrationWarning>{new Date().toLocaleString()}</span>``` #### rendering-client-only - @rules/rendering-client-only.md Render browser-only components with ClientOnly and a fallback. ```tsx<ClientOnly fallback={<Skeleton />}>  {() => <Map />}</ClientOnly>``` #### rendering-use-hydrated - @rules/rendering-use-hydrated.md Use `useHydrated` for SSR/CSR divergence. ```tsxlet hydrated = useHydrated();return hydrated ? <Widget /> : <Skeleton />;``` #### rendering-usetransition-loading - @rules/rendering-usetransition-loading.md Prefer useTransition over manual loading states. ```tsxconst [isPending, startTransition] = useTransition(); let handleSearch = (value) => {  startTransition(async () => {    let data = await fetchResults(value);    setResults(data);  });};``` #### fault-tolerant-error-boundaries - @rules/fault-tolerant-error-boundaries.md Place error boundaries at feature boundaries. ```tsx<ErrorBoundary fallback={<SidebarError />}>  <Sidebar /></ErrorBoundary>``` ### Client Patterns (MEDIUM) #### client-passive-event-listeners - @rules/client-passive-event-listeners.md Use passive listeners for scroll/touch. ```tsxdocument.addEventListener("wheel", handler, { passive: true });document.addEventListener("touchstart", handler, { passive: true });``` #### client-localstorage-schema - @rules/client-localstorage-schema.md Version and minimize localStorage data. ```typescriptconst VERSION = "v2"; function saveConfig(config: Config) {  try {    localStorage.setItem(`config:${VERSION}`, JSON.stringify(config));  } catch {} // Handle incognito/quota exceeded}``` ### Hooks (HIGH) #### hooks-limit-useeffect - @rules/hooks-limit-useeffect.md Use useEffect only when absolutely necessary. Prefer derived state or event handlers. ```tsx// Bad: useEffect to derive statelet [filtered, setFiltered] = useState(items);useEffect(() => {  setFiltered(items.filter((i) => i.active));}, [items]); // Good: derive during renderlet filtered = items.filter((i) => i.active); // Good: useMemo if expensivelet filtered = useMemo(() => items.filter((i) => i.active), [items]);``` #### hooks-useeffect-named-functions - @rules/hooks-useeffect-named-functions.md Use named function declarations in useEffect for better debugging and self-documentation. ```tsx// Bad: anonymous arrow functionuseEffect(() => {  document.title = title;}, [title]); // Good: named functionuseEffect(  function syncDocumentTitle() {    document.title = title;  },  [title],); // Good: also name cleanup functionsuseEffect(function subscribeToOnlineStatus() {  window.addEventListener("online", handleOnline);  return function unsubscribeFromOnlineStatus() {    window.removeEventListener("online", handleOnline);  };}, []);``` ### Composition Patterns (HIGH) #### composition-avoid-boolean-props - @rules/composition-avoid-boolean-props.md Don't add boolean props to customize behavior. Use composition instead. ```tsx// Bad: boolean prop explosion<Composer isThread isEditing={false} showAttachments /> // Good: explicit variants<ThreadComposer channelId="abc" /><EditComposer messageId="xyz" />``` #### composition-compound-components - @rules/composition-compound-components.md Structure complex components as compound components with shared context. ```tsx// Good: compound components<Composer.Provider state={state} actions={actions}>  <Composer.Frame>    <Composer.Input />    <Composer.Footer>      <Composer.Submit />    </Composer.Footer>  </Composer.Frame></Composer.Provider>``` #### composition-state-provider - @rules/composition-state-provider.md Lift state into provider components for cross-component access. ```tsx// Good: state in provider, accessible anywhere inside<ForwardMessageProvider>  <Dialog>    <Composer.Input />    <MessagePreview /> {/* Can read state */}    <ForwardButton /> {/* Can call submit */}  </Dialog></ForwardMessageProvider>``` #### composition-explicit-variants - @rules/composition-explicit-variants.md Create explicit variant components instead of prop combinations. ```tsx// Good: self-documenting variantsfunction ThreadComposer({ channelId }) {  return (    <ThreadProvider channelId={channelId}>      <Composer.Frame>        <Composer.Input />        <AlsoSendToChannelField />        <Composer.Submit />      </Composer.Frame>    </ThreadProvider>  );}``` #### composition-children-over-render-props - @rules/composition-children-over-render-props.md Prefer children for composition. Use render props only when passing data back. ```tsx// Good: children for structure<Card>  <Card.Header>Title</Card.Header>  <Card.Body>Content</Card.Body></Card> // OK: render props when passing data<List renderItem={({ item }) => <Item {...item} />} />``` #### composition-avoid-overabstraction - @rules/composition-avoid-overabstraction.md Avoid rigid configuration props; prefer composable children APIs. ```tsx<Select value="abc" onChange={...}>  <Option value="abc">ABC</Option>  <Option value="xyz">XYZ</Option></Select>``` #### composition-typescript-namespaces - @rules/composition-typescript-namespaces.md Use TypeScript namespaces to combine component and its types for single-import access. ```tsx// components/button.tsxexport namespace Button {  export type Variant = "solid" | "ghost" | "outline";  export interface Props {    variant?: Variant;    children: React.ReactNode;  }} export function Button({ variant = "solid", children }: Button.Props) {  // ...} // Usage: single importimport { Button } from "~/components/button"; <Button variant="ghost">Click</Button>function wrap(props: Button.Props) { ... }``` **Important:** Namespaces should only contain types, never runtime code.