--- name: react-use-client-boundary description: Guides proper usage of "use client" directive in React/Next.js. Use this skill when adding client components, troubleshooting Server Component errors, or deciding where to place the client boundary. --- # React "use client" Directive & Client Boundaries Understanding when to use (and when NOT to use) the "use client" directive in React Server Components architecture. ## Core Concept: The Boundary `"use client"` marks a **boundary** between server and client components - not a label for individual components. **Critical Rule:** Once inside a client boundary, ALL imported components are automatically client components. You should NOT add `"use client"` to child components that are already imported by a parent client component. ## Mental Model: The Fence Think of `"use client"` as a **fence** or **gate**: ``` ┌─────────────────────────────────────────────────────┐ │ SERVER TERRITORY │ │ ┌─────────────┐ │ │ │ page.tsx │ (Server Component - default) │ │ │ │ │ │ │
│───────────────────────┐ │ │ └─────────────┘ │ │ │ ▼ │ │ ════════════════ "use client" FENCE ════════════ │ │ │ │ │ ┌─────────────────────────────────────┼──────────┐ │ │ │ CLIENT TERRITORY ▼ │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ Header.tsx │───▶│ NavMenu.tsx │ │ │ │ │ │"use client" │ │ (no directive│ │ │ │ │ │ │ │ needed!) │ │ │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ │ You're already inside - no more fences needed │ │ │ └─────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────┘ ``` ## When to Use "use client" Add the directive when ALL of these are true: 1. **The component is imported by a Server Component** (directly or as a page entry) 2. **AND** the component needs client-side features: - React hooks (`useState`, `useEffect`, `useContext`, etc.) - Event handlers (`onClick`, `onChange`, `onSubmit`, etc.) - Browser APIs (`window`, `document`, `localStorage`, etc.) - Third-party libraries that use any of the above ## When NOT to Use "use client" 1. **Already inside a client boundary** - parent component has `"use client"` 2. **Component is pure presentation** - just renders props, no interactivity 3. **"Just to be safe"** - this creates confusion and unnecessary boundaries 4. **Every component that uses props** - props work fine in server components ## Common Mistake: Redundant Directives ```tsx // ❌ WRONG: Unnecessary "use client" in child // components/form.tsx "use client" import { Input } from "./input" import { Button } from "./button" export function Form() { const [value, setValue] = useState("") return (
) } // components/input.tsx "use client" // ❌ WRONG - already a client component! export function Input({ value, onChange }) { return onChange(e.target.value)} /> } // components/button.tsx "use client" // ❌ WRONG - already a client component! export function Button({ children, type }) { return } ``` ## Correct Approach: Single Boundary ```tsx // ✅ CORRECT: Only the entry point has "use client" // components/form.tsx "use client" import { Input } from "./input" import { Button } from "./button" export function Form() { const [value, setValue] = useState("") return (
) } // components/input.tsx // ✅ No directive - imported by client component export function Input({ value, onChange }) { return onChange(e.target.value)} /> } // components/button.tsx // ✅ No directive - imported by client component export function Button({ children, type }) { return } ``` ## Decision Flowchart ``` Is this component imported by a Server Component? │ ├─ NO ──▶ Is its parent/importer a Client Component? │ │ │ ├─ YES ──▶ ❌ Don't add "use client" (already in boundary) │ │ │ └─ NO ───▶ Check the import chain upward │ └─ YES ─▶ Does this component need client features? │ ├─ NO ──▶ ❌ Don't add "use client" (keep it server) │ └─ YES ─▶ ✅ Add "use client" (create boundary here) ``` ## Real-World Example: Page with Interactive Section ```tsx // app/products/page.tsx (Server Component - no directive) import { ProductList } from "@/components/product-list" import { SearchFilters } from "@/components/search-filters" import { getProducts } from "@/lib/api" export default async function ProductsPage() { const products = await getProducts() // Server-side data fetching return (

Products

{/* Client boundary starts here */} {/* Server component */}
) } // components/search-filters.tsx "use client" // ✅ Boundary: imported by server, needs state import { FilterDropdown } from "./filter-dropdown" import { PriceSlider } from "./price-slider" export function SearchFilters() { const [filters, setFilters] = useState({}) return (
{/* No directive needed */} {/* No directive needed */}
) } // components/filter-dropdown.tsx // ✅ No "use client" - already inside client boundary export function FilterDropdown({ onSelect }) { return } // components/price-slider.tsx // ✅ No "use client" - already inside client boundary export function PriceSlider({ onChange }) { return onChange(e.target.value)} /> } ``` ## Edge Case: Shared Components When a component is used by BOTH server and client components: ```tsx // components/card.tsx // No directive - works in both contexts if it's pure presentation export function Card({ title, children }) { return (

{title}

{children}
) } // app/page.tsx (Server Component) import { Card } from "@/components/card" // Card renders as server component here // components/modal.tsx "use client" import { Card } from "@/components/card" // Card renders as client component here (inside boundary) ``` ## Troubleshooting Common Errors ### Error: "useState only works in Client Components" **Cause:** Using hooks in a component without `"use client"` that's imported by a server component. **Fix:** Add `"use client"` to the component using the hook, OR move the hook usage to a parent client component. ### Error: "Event handlers cannot be passed to Client Components from Server Components" **Cause:** Trying to pass a function from server to client component. **Fix:** Move the event handler logic to the client component, or restructure the boundary. ### Error: "async/await is not yet supported in Client Components" **Cause:** Using async component syntax inside a client boundary. **Fix:** Keep data fetching in server components, pass data as props to client components. ## Best Practices Summary | Do | Don't | |---|---| | Place `"use client"` at the highest necessary point | Sprinkle `"use client"` on every component | | Keep the client boundary as small as possible | Make entire pages client components | | Let child components inherit client context | Add redundant `"use client"` to children | | Use server components for data fetching | Fetch data in client components when avoidable | ## References - [React Docs: "use client"](https://react.dev/reference/rsc/use-client) - [Next.js Discussion: Client Component Boundaries](https://github.com/vercel/next.js/discussions/46795)