Chapter
02 · Client Component (UI & Interaction)
Interactive UI only: render/react and call server actions; never fetch or decide business rules.
Mental Model
- Client Components speak to users, not databases.
- They render, react, and request mutations; they never decide business rules.
- They are interactive and replaceable; they do not own data.
File Classification
Layer: UI / Interaction
Directive: "use client"
Runtime: Browser
Reads: props or promises only
Writes: ✅ via Server Actions
Prisma: ❌ Never
API Routes: ❌ Never (internal UI)
Should Do
- Render interactive UI (presentation + events).
- Consume server data via props or promise + use().
- Trigger mutations via Server Actions (buttons/forms).
- Use context only for UI state (theme, edit mode, filters).
Should Not (and where it belongs)
- Prisma/DB fetch → put in server actions.
- Business rules → entity/domain layer, not UI.
- Internal API fetch → call server actions directly.
- Owning entity truth → keep on the server; client state is UI-only.
Canonical Example
"use client";
import { use } from "react";
import { deleteProject } from "../actions/deleteProject";
import type { Project } from "../types";
export default function ProjectsClient({ projectsPromise }: { projectsPromise: Promise<Project[]>; }) {
const projects = use(projectsPromise);
return (
<ul>
{projects.map((p) => (
<li key={p.id}>
{p.name}
<button onClick={() => deleteProject(p.id)}>Delete</button>
</li>
))}
</ul>
);
}Form + Server Action
"use client";
import { createProject } from "../actions/createProject";
export function ProjectForm() {
async function onSubmit(formData: FormData) {
await createProject(formData);
}
return (
<form action={onSubmit}>
<input name="name" />
<button type="submit">Create</button>
</form>
);
}
Colour-Coded Miniature
"use client";
import { deleteProject } from "../actions/deleteProject";
export function ProjectRow({ project }) {
return (
<div>
{project.name}
<button onClick={() => deleteProject(project.id)}>Delete</button>
</div>
);
}Quick Rules
- Client Components render/handle UX; they never fetch or decide.
- Mutations go through Server Actions; no fetch to internal APIs.
- Context is for UI state only; entity truth stays on the server.
- If it feels “dumb but responsive,” it’s correct.