React dengan Claude Code untuk produksi: komponen, Hooks, test, dan review
Gunakan Claude Code untuk React yang aman: komponen typed, Hooks, test, aksesibilitas, performa, dan review.
Claude Code bisa mempercepat development React, tetapi tim produksi tidak hanya butuh cepat. Tim butuh perubahan kecil, mudah direview, dan bisa dibuktikan lewat test. Jika prompt terlalu umum seperti “buat dashboard yang bagus”, hasilnya sering berupa terlalu banyak komponen, terlalu banyak props, dan sedikit bukti kualitas.
Panduan ini membahas workflow React + TypeScript: batas komponen, props, state, custom hook, form, data fetching, Testing Library, aksesibilitas, performa, dan prompt review. Jadikan sumber resmi sebagai baseline: React docs, React Testing Library, MDN ARIA, dan Claude Code docs. Untuk konteks lokal ClaudeCodeLab, lanjutkan ke TypeScript tips, strategi testing, aksesibilitas, dan optimasi performa.
Mulai dari kontrak
Props adalah input dari parent ke child. State adalah nilai yang diingat UI. Custom hook adalah fungsi reusable untuk logic state dan side effect. Side effect mencakup API call, timer, dan localStorage.
| Informasi | Contoh | Manfaat |
|---|---|---|
| Tanggung jawab UI | daftar user, filter role, toggle active | mencegah komponen terlalu besar |
| Lokasi state | parent, URL, hook, server cache | mencegah useState tersebar |
| Bentuk data | tipe User, API response, empty state | props dan test lebih jelas |
| Verifikasi | npm test, npm run build, keyboard | ada bukti untuk review |
Use case nyata
Pertama, internal admin list: user, invoice, ticket, role, atau product. Claude Code bisa memisahkan filter, table, row action, empty state, dan error state tanpa mengubah kontrak API.
Kedua, form untuk profile edit, contact, checkout, atau signup training. Prompt harus menyebut label, validasi, status submitting, dan retry. Untuk form yang lebih besar, baca React Hook Form dengan Claude Code.
Ketiga, layar data fetching seperti search results, notifications, dan dashboard. Server state jangan dicampur dengan UI state sementara. Untuk TanStack Query, lihat TanStack Query; untuk client state kecil, lihat Zustand.
Keempat, review diff. Minta Claude Code memeriksa batas komponen, Effect yang tidak perlu, label yang hilang, test rapuh, dan re-render yang bisa dihindari.
Diagram komponen
UserAdminPage
-> UserFilters: search dan role filter
-> UserTable: table dan actions
-> UserStatusBadge: hanya status display
-> UserEditDialog: form edit
-> useUsers: fetch, refresh, error
Jika props sebuah komponen tidak bisa dijelaskan dalam satu kalimat, kemungkinan komponen itu melakukan terlalu banyak hal. Jika komponen hanya dipakai sekali dan tidak punya behavior, extraction mungkin terlalu cepat.
Komponen React typed
import type { FormEvent } from "react";
export type UserRole = "admin" | "editor" | "viewer";
export type User = {
id: string;
name: string;
email: string;
role: UserRole;
active: boolean;
};
type UserTableProps = {
users: User[];
selectedRole: "all" | UserRole;
onRoleChange: (role: "all" | UserRole) => void;
onToggleActive: (id: string) => void;
};
export function UserTable({ users, selectedRole, onRoleChange, onToggleActive }: UserTableProps) {
const filteredUsers = selectedRole === "all" ? users : users.filter((user) => user.role === selectedRole);
function handleRoleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
onRoleChange(formData.get("role") as "all" | UserRole);
}
return (
<section aria-labelledby="user-table-title">
<h2 id="user-table-title">Team members</h2>
<form onSubmit={handleRoleSubmit} style={{ marginBottom: 12 }}>
<label htmlFor="role">Filter by role </label>
<select id="role" name="role" defaultValue={selectedRole}>
<option value="all">All</option>
<option value="admin">Admin</option>
<option value="editor">Editor</option>
<option value="viewer">Viewer</option>
</select>
<button type="submit">Apply</button>
</form>
{filteredUsers.length === 0 ? (
<p role="status">No users match this filter.</p>
) : (
<table>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Role</th>
<th scope="col">Status</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{filteredUsers.map((user) => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.role}</td>
<td>{user.active ? "Active" : "Paused"}</td>
<td>
<button type="button" onClick={() => onToggleActive(user.id)}>
{user.active ? "Pause" : "Activate"}
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</section>
);
}
Hook dan test
import { useEffect, useState } from "react";
import type { User } from "./UserTable";
type UsersState =
| { status: "loading"; users: User[]; error: null }
| { status: "success"; users: User[]; error: null }
| { status: "error"; users: User[]; error: string };
export function useUsers(endpoint: string) {
const [state, setState] = useState<UsersState>({ status: "loading", users: [], error: null });
useEffect(() => {
const controller = new AbortController();
async function loadUsers() {
setState({ status: "loading", users: [], error: null });
try {
const response = await fetch(endpoint, { signal: controller.signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const users = (await response.json()) as User[];
setState({ status: "success", users, error: null });
} catch (error) {
if (error instanceof DOMException && error.name === "AbortError") return;
setState({ status: "error", users: [], error: error instanceof Error ? error.message : "Unknown error" });
}
}
void loadUsers();
return () => controller.abort();
}, [endpoint]);
return state;
}
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { UserTable, type User } from "./UserTable";
const users: User[] = [
{ id: "1", name: "Masa", email: "masa@example.com", role: "admin", active: true },
{ id: "2", name: "Aiko", email: "aiko@example.com", role: "viewer", active: false },
];
describe("UserTable", () => {
it("filters users and toggles active status", async () => {
const user = userEvent.setup();
const onRoleChange = vi.fn();
const onToggleActive = vi.fn();
render(<UserTable users={users} selectedRole="all" onRoleChange={onRoleChange} onToggleActive={onToggleActive} />);
await user.selectOptions(screen.getByLabelText(/filter by role/i), "viewer");
await user.click(screen.getByRole("button", { name: /apply/i }));
await user.click(screen.getByRole("button", { name: /activate/i }));
expect(onRoleChange).toHaveBeenCalledWith("viewer");
expect(onToggleActive).toHaveBeenCalledWith("2");
});
});
Prompt review
Review diff React + TypeScript ini.
Periksa batas komponen, props yang berlebihan, useEffect yang tidak perlu, campuran UI state dan server state, label dan error form, coverage Testing Library, serta risiko re-render pada list besar.
Jangan tambah library baru. Akhiri dengan checklist npm test dan npm run build.
Pitfall umum
Pitfall yang sering muncul: komponen terlalu banyak, useEffect untuk nilai turunan, input tanpa label, icon button tanpa accessible name, error hanya dengan warna, dan optimasi performa tanpa pengukuran. Prompt yang baik meminta abstraksi paling kecil yang berguna, test dari sudut pandang user, dan cek aksesibilitas.
CTA dan hasil
Untuk tim, tulis aturan ini di CLAUDE.md: batas komponen, command test, aksesibilitas, dan batas over-abstraction. Training dan konsultasi Claude Code dari ClaudeCodeLab bisa membantu menerapkan workflow ini di repo React nyata, termasuk UI review, Testing Library, performance proof, dan CTA untuk konsultasi atau penjualan.
Hasil praktik: saat Masa memakai alur ini untuk UI admin situs konten, cleanup props dan revisi aksesibilitas berkurang karena boundary, ownership state, dan ekspektasi test sudah diberikan sebelum code generation. Pola yang paling stabil adalah diff kecil yang bisa diverifikasi.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Workflow Obsidian ke CLAUDE.md untuk Claude Code
Ubah catatan kerja Obsidian menjadi operating note CLAUDE.md agar konteks tidak dijelaskan ulang.
Claude Code Revenue CTA Routing: dari artikel ke PDF, Gumroad, dan konsultasi
Workflow Claude Code untuk mengarahkan pembaca ke PDF gratis, Gumroad, atau konsultasi sesuai intent.
Aturan handoff tim Claude Code: bukti review, permission, rollback, dan jalur revenue
Format handoff Claude Code untuk tim: bukti, permission rule, rollback, PDF gratis, Gumroad, dan konsultasi.