From ccube-frontend-dev
React beginner fundamentals — JSX, functional components, props, state, conditional rendering, list rendering, useEffect, data fetching, component composition, and common rookie mistakes. Use when implementing basic React features or when the user is new to React.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ccube-frontend-dev:cc-react-beginnerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Quick-reference for React fundamentals. This skill covers the
Quick-reference for React fundamentals. This skill covers the building blocks every React developer must know before writing production code.
Version baseline: Targets React 18.x. Hooks require React
16.8+. See cc-react-18-patterns for React 18-specific
concurrent rendering patterns.
JSX is syntactic sugar for React.createElement. It looks like
HTML but has key differences:
className instead of class<br />, <img />{curly braces}<>...</> (Fragment) to avoid adding extra DOM nodesfunction Greeting({ name }: { name: string }) {
const isLoggedIn = true;
return (
<>
<h1>Hello, {name}!</h1>
{isLoggedIn && <p>Welcome back.</p>}
</>
);
}
Key point: JSX attributes use camelCase:
onClick, onChange, htmlFor, tabIndex.
A component is a function that accepts props and returns JSX.
// Named function (recommended — better stack traces)
function Badge({ label }: { label: string }) {
return <span className="badge">{label}</span>;
}
// Arrow function variant (equivalent)
const Badge = ({ label }: { label: string }) => (
<span className="badge">{label}</span>
);
Rules:
<badge /> is treated as a DOM element; <Badge /> is a
componentProps are read-only inputs passed from parent to child.
interface UserCardProps {
name: string;
role: string;
avatarUrl?: string; // optional prop
}
function UserCard({ name, role, avatarUrl }: UserCardProps) {
return (
<div>
{avatarUrl && <img src={avatarUrl} alt={name} />}
<h2>{name}</h2>
<p>{role}</p>
</div>
);
}
// Usage
<UserCard name="Alice" role="Engineer" />
Key points:
{ size = "md" }function Card({ children }: { children: React.ReactNode }) {
return <div className="card">{children}</div>;
}
// Usage
<Card>
<h2>Title</h2>
<p>Content goes here.</p>
</Card>
useState)State is local, mutable data that causes a re-render when it changes.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
);
}
Rules:
count++ — always call the
setter: setCount(count + 1)setCount(prev => prev + 1)// Wrong — mutates existing object
user.name = "Alice";
setUser(user);
// Correct — creates a new object
setUser({ ...user, name: "Alice" });
// Wrong — mutates existing array
items.push(newItem);
setItems(items);
// Correct — creates a new array
setItems([...items, newItem]);
function Status({ isLoggedIn }: { isLoggedIn: boolean }) {
// if/else for complex conditions
if (!isLoggedIn) {
return <p>Please log in.</p>;
}
return (
<div>
{/* && for "show if true" */}
{isLoggedIn && <p>Welcome!</p>}
{/* Ternary for "show A or B" */}
{isLoggedIn ? <Dashboard /> : <LoginPage />}
</div>
);
}
Key points:
{count && <p>Count: {count}</p>} is a trap when
count is 0 — React renders 0 (falsy number) as a
visible character. Use {count > 0 && ...} or a ternary.null renders nothing: if (!data) return null;Use .map() to render lists. Every item needs a unique key.
interface Product {
id: string;
name: string;
price: number;
}
function ProductList({ products }: { products: Product[] }) {
return (
<ul>
{products.map((product) => (
<li key={product.id}>
{product.name} — ${product.price}
</li>
))}
</ul>
);
}
Key rules:
key must be unique among siblings, stable, and from your
data (e.g., a database ID)key is not accessible as a prop inside the componentuseEffect)useEffect runs code after render — use it to sync with
external systems (APIs, subscriptions, DOM manipulation).
import { useEffect, useState } from "react";
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
useEffect(() => {
// Runs after every render where userId changed
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then(setUser);
// Cleanup runs before the next effect or on unmount
return () => {
// cancel requests, clear timers, remove listeners
};
}, [userId]); // dependency array
if (!user) return <p>Loading...</p>;
return <p>{user.name}</p>;
}
Dependency array rules:
[] — run after the initial mount (in development Strict
Mode, React runs setup + cleanup + setup one extra time)[userId] — run when userId changeseslint-plugin-react-hooks to catch violationsDo NOT use useEffect for:
Prefer a library (TanStack Query, SWR) for production. For
simple cases, use useEffect with abort cleanup:
import { useEffect, useState } from "react";
function useUser(userId: string) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(setUser)
.catch((err) => {
if (err.name !== "AbortError") setError(err);
})
.finally(() => setLoading(false));
return () => controller.abort();
}, [userId]);
return { user, loading, error };
}
Break UIs into small, focused components. Each component does one thing.
// Bad: one giant component does everything
function Page() {
return (
<div>
{/* 200 lines of JSX */}
</div>
);
}
// Good: composed from smaller focused pieces
function Page() {
return (
<Layout>
<Header />
<main>
<Sidebar />
<ArticleList />
</main>
<Footer />
</Layout>
);
}
When to split a component:
When two siblings need to share state, lift it to their common parent.
function FilterableList() {
// State lives here — shared between Filter and List
const [filter, setFilter] = useState("");
return (
<div>
<Filter value={filter} onChange={setFilter} />
<ItemList filter={filter} />
</div>
);
}
Mutating state directly — state.value = x instead of
calling the setter; the component will not re-render
Using index as list key — causes ghost UI bugs when items are added, removed, or reordered
Falsy number 0 in JSX —
{count && <p>...</p>} renders 0 to the screen when
count is 0; use a ternary or {count > 0 && ...}
Missing effect cleanup — not returning a cleanup function causes memory leaks and stale data after unmount
Missing deps in useEffect — causes stale closures;
always install eslint-plugin-react-hooks
Fetching without cleanup — if the component unmounts
before the fetch resolves, stale async work can update the
wrong UI state; add cancellation/ignore cleanup
(AbortController, ignore flag, or library-managed cleanup)
Defining components inside other components — a component defined inside render is recreated on every render, breaking keys, focus, and state; move it outside
// Wrong: ListItem is recreated on every render of List
function List({ items }) {
const ListItem = ({ item }) => <li>{item.name}</li>;
return <ul>{items.map((i) => <ListItem key={i.id} item={i} />)}</ul>;
}
// Correct: define ListItem outside List
const ListItem = ({ item }) => <li>{item.name}</li>;
function List({ items }) { ... }
Untyped props — relying on implicit any; always
define a TypeScript interface for props
Prop drilling too deep — passing props through 4+ component layers; reach for Context or a state library
Over-using useEffect — most derived data should be
computed directly in render, not synced via effect
| Scenario | Solution |
|---|---|
| Show/hide an element | Conditional rendering (&& or ternary) |
| Render a list | .map() with stable key from data |
| Track user input | useState |
| Fetch data on mount | useEffect(() => {...}, []) |
| Fetch when a value changes | useEffect(() => {...}, [value]) |
| Share state between siblings | Lift state to parent |
| Share state across the tree | Context + useContext |
| Reuse logic across components | Custom hook (useXxx) |
| Avoid prop drilling | Context or state library |
| Complex state transitions | useReducer |
npx claudepluginhub lifesg/ccube-agent-plugin-marketplace --plugin ccube-frontend-devProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.