Skip to content

Client Components Deep Dive — Interactivity in the Browser

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Client Components Deep Dive. We cover key concepts, practical examples, and best practices to help you master this topic.

Client Components run in the browser with full access to React hooks, browser APIs, and user interaction handlers, providing the interactive layer of your application.

What You'll Learn

You will understand how Client Components work, what they can and cannot do, how they receive data from Server Components, and patterns for keeping them lean.

Why It Matters

Client Components handle everything interactive in your app. Understanding their capabilities and limitations ensures you use them only where needed and keep the client bundle small.

Real-World Use

DodaZIP's file manager uses Client Components only for the drag-and-drop upload zone, the rename modal, and the context menu. The file tree, search results, and metadata panels are Server Components.

flowchart TD
    A[Server Component] --> B[Fetch Data]
    A --> C[Render Static HTML]
    D[Client Component] --> E[useState for UI state]
    D --> F[useEffect for side effects]
    D --> G[Event Handlers]
    D --> H[Browser APIs]
    B --> I[Pass data as props]
    I --> D
    style A fill:#1e293b,color:#fff
    style D fill:#0f172a,color:#fff

What Client Components Can Do

Client Components have full access to React hooks, event handlers, and browser APIs.

'use client';
import { useState, useEffect, useRef } from 'react';

export default function ThreatMonitor() {
  const [threats, setThreats] = useState([]);
  const [filter, setFilter] = useState('all');
  const intervalRef = useRef(null);

  useEffect(() => {
    intervalRef.current = setInterval(async () => {
      const res = await fetch('/api/threats/recent');
      const data = await res.json();
      setThreats(data);
    }, 5000);
    return () => clearInterval(intervalRef.current);
  }, []);

  const filteredThreats = threats.filter(t =>
    filter === 'all' ? true : t.severity === filter
  );

  return (
    <div>
      <select onChange={e => setFilter(e.target.value)}>
        <option value="all">All Threats</option>
        <option value="CRITICAL">Critical</option>
        <option value="HIGH">High</option>
      </select>
      <ul>
        {filteredThreats.map(t => (
          <li key={t.id} style={{ color: t.severity === 'CRITICAL' ? 'red' : 'orange' }}>
            {t.name}  {t.severity}
          </li>
        ))}
      </ul>
    </div>
  );
}

Expected output: A live threat monitor that polls the server every 5 seconds. The dropdown filters threats by severity. The component has full access to useState, useEffect, and useRef.

Receiving Props from Server Components

Client Components receive serialized props from Server Components. This is the primary data flow pattern.

// Client Component
'use client';
function UserCard({ user, isAdmin }) {
  const [expanded, setExpanded] = useState(false);
  return (
    <div onClick={() => setExpanded(!expanded)}>
      <h3>{user.name}</h3>
      {isAdmin && <span>Admin</span>}
      {expanded && <p>{user.bio}</p>}
    </div>
  );
}

// Server Component
async function UsersPage() {
  const users = await db.users.findAll();
  return (
    <div>
      {users.map(u => (
        <UserCard key={u.id} user={u} isAdmin={u.role === 'admin'} />
      ))}
    </div>
  );
}

Expected output: The UsersPage fetches all users on the server and renders a UserCard for each. The cards display a name and optional Admin badge. Clicking a card toggles the bio visibility. All user data is serialized from server to client.

Client Component Composition with Server Children

Client Components can receive Server Components as children, creating a powerful composition pattern.

'use client';
function CollapsibleSection({ title, children }) {
  const [isOpen, setIsOpen] = useState(true);
  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>
        {title} {isOpen ? '▼' : '▶'}
      </button>
      {isOpen && <div>{children}</div>}
    </div>
  );
}

// Usage in a Server Component
async function DocumentationPage() {
  const sections = await db.docs.findAll();
  return (
    <div>
      {sections.map(s => (
        <CollapsibleSection key={s.id} title={s.title}>
          <ServerRenderedContent content={s.body} />
        </CollapsibleSection>
      ))}
    </div>
  );
}

Expected output: The Client Component controls the collapse state while the child Server Component handles content rendering. The children are passed as JSX and never need to be serialized.

Browser APIs in Client Components

Client Components can access localStorage, navigator, IntersectionObserver, and other browser APIs.

'use client';
import { useState, useEffect } from 'react';

function InstallPrompt() {
  const [deferredPrompt, setDeferredPrompt] = useState(null);
  const [installed, setInstalled] = useState(false);

  useEffect(() => {
    const handler = (e) => {
      e.preventDefault();
      setDeferredPrompt(e);
    };
    window.addEventListener('beforeinstallprompt', handler);
    return () => window.removeEventListener('beforeinstallprompt', handler);
  }, []);

  async function handleInstall() {
    if (deferredPrompt) {
      deferredPrompt.prompt();
      const result = await deferredPrompt.userChoice;
      if (result.outcome === 'accepted') {
        setInstalled(true);
      }
    }
  }

  if (installed || !deferredPrompt) return null;
  return <button onClick={handleInstall}>Install App</button>;
}

Expected output: An install prompt button that appears only when the beforeinstallprompt event fires. Clicking it triggers the browser's native install dialog. After installation, the button disappears.

Common Mistakes

  1. Putting too much logic in Client Components: Move data fetching and heavy computation to Server Components. Client Components should handle only interactivity and UI state.

  2. Forgetting to mark the file with use client: Using hooks without 'use client' throws a build error. Always add the directive when using hooks or browser APIs.

  3. Accessing browser APIs during SSR: Browser APIs like window and document are not available during server rendering. Use useEffect or check for typeof window !== 'undefined'.

  4. Passing non-serializable props from Server Components: Functions, class instances, and symbols cannot be passed. Ensure all props are plain objects, arrays, strings, numbers, booleans, or null.

  5. Not handling hydration errors: Client Components hydrate on the client. If server HTML differs from client render, hydration errors occur. Ensure consistent rendering.

Practice Questions

  1. What directive is required for Client Components?

The 'use client' directive at the top of the file. Without it, components are Server Components by default.

  1. Can Client Components use server-only modules like fs or crypto?

No. Client Components run in the browser. Server-only modules are not available. Import them only in Server Components.

  1. How do Client Components receive data?

Through serialized props passed from Server Components, or through client-side data fetching with useEffect and fetch.

  1. What is the composition pattern for Client Components with Server children?

Client Components accept children or props that are Server Components. The children are rendered without Serialization.

  1. Why should you push Client Component boundaries deep?

To minimize the JavaScript bundle. Only the deep interactive leaves ship to the client. Everything above stays as Server Components.

Challenge

Build a tabbed interface where the tabs (Client Component) control which content panel is visible, but each panel's content is a Server Component that fetches and renders its own data.

Frequently Asked Questions

Do Client Components support server-side rendering?

Yes. Client Components are still server-rendered to HTML. The HTML is sent to the client, and then React hydrates the component to make it interactive.

Can I use Context in Client Components?

Yes. React Context works in Client Components. Context providers must be in Client Components because they manage state that changes over time.

How do I lazy load Client Components?

Use next/dynamic to dynamically import Client Components. This splits their code into a separate chunk that loads on demand.

Can Client Components use CSS modules?

Yes. CSS modules work in both Server and Client Components. CSS-in-JS libraries like styled-components require Client Components.

What happens to Client Components during navigation?

Client Components are cached between navigations. Their state persists unless the component unmounts or the page does a full reload.

Mini Project

Create a file browser UI where the file tree (Server Component) fetches the directory structure from the database, and the file viewer (Client Component) handles file selection, preview toggling, and the download button.

What's Next

Learn how to Composing Server and Client effectively for Clean Architecture.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro