React Conventions Skill
What This Skill Does
Captures React best practices for this project with emphasis on React Compiler compatibility:
- Compiler-ready code - No manual memoization (
useMemo,useCallback,memo) - Functional components - Plain functions, hooks-based patterns only
- Styling patterns - Tailwind CSS integration and best practices
- Component organization - One main export per file, colocated tests
- Hook patterns - Proper dependency arrays, no stale closures
- Type safety - Strong prop typing with TypeScript
When to Use
- Authoring new React components, hooks, or pages
- Reviewing PRs for React patterns and performance
- Implementing component-specific styling or state management
- Migrating code to React Compiler (removing manual optimizations)
- Creating custom hooks or composable logic
Key Rules
1. React Compiler Ready
Never use manual memoization - React Compiler handles optimization automatically:
// ❌ BAD: Unnecessary memoization
const MyComponent = memo(() => {
return <div>Content</div>;
});
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
const computedValue = useMemo(() => expensiveComputation(), [deps]);
✅ GOOD: Plain functions - Compiler optimizes automatically:
function MyComponent(): JSX.Element {
// Plain function - no memo needed
return <div>Content</div>;
}
function handleClick(): void {
// Plain function - no useCallback needed
console.log("clicked");
}
const computedValue = expensiveComputation(); // No useMemo needed
When to break this rule: Only if there's documented evidence of performance regression. Then add a comment explaining why:
// NOTE: Profiling showed unnecessary re-renders (see GitHub issue #123).
// Memoization reduces re-renders from 5x to 1x in this scenario.
const MemoizedComponent = memo(ExpensiveComponent);
2. Functional Components Only
All components are functional components with hooks:
// ❌ BAD: Class components
class UserProfile extends React.Component {
render() {
return <div>{this.props.name}</div>;
}
}
// ✅ GOOD: Functional component
function UserProfile(props: UserProfileProps): JSX.Element {
return <div>{props.name}</div>;
}
3. Hook Patterns
Proper dependency arrays:
// ❌ BAD: Missing or incorrect dependencies
useEffect(() => {
fetchUser(userId); // userId not in deps - stale closure!
}, []);
// ✅ GOOD: Complete dependency array
useEffect(() => {
fetchUser(userId);
}, [userId]); // userId included
No hook rules violations:
// ❌ BAD: Hook inside conditional
if (user.isActive) {
useState("initial"); // NEVER - breaks hook rules
}
// ✅ GOOD: Hooks at top level
const [state, setState] = useState("initial");
4. Component Organization
One main component per file:
// ✅ GOOD: SongCard.tsx - single responsibility
export function SongCard(props: SongCardProps): JSX.Element {
return <div>{/* ... */}</div>;
}
// ✅ GOOD: Colocated test
// SongCard.test.tsx in same directory
Colocated tests:
react/src/components/
SongCard.tsx
SongCard.test.tsx ← Same directory
UserMenu.tsx
UserMenu.test.tsx
5. Prop Typing
Always type component props:
// ❌ BAD: No prop types
function Button(props) {
return <button>{props.label}</button>;
}
// ✅ GOOD: Explicit prop type
type ButtonProps = {
label: string;
onClick?: () => void;
disabled?: boolean;
};
function Button({ label, onClick, disabled }: ButtonProps): JSX.Element {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
}
6. State Management
Use Zustand for shared state:
// ✅ GOOD: Zustand store with selector
export const useAppStore = create((set) => ({
user: null,
setUser: (user) => set({ user }),
}));
// In component - use selector to avoid unnecessary re-renders
function MyComponent() {
const user = useAppStore((state) => state.user);
return <div>{user?.name}</div>;
}
Local state with useState for component-local data:
// ✅ GOOD: Local state for form
function LoginForm(): JSX.Element {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = async () => {
// Use email and password
};
See Zustand patterns guide for detailed store patterns, async operations, middleware, and testing.
return ...; }
### 7. Styling with Tailwind
**Use Tailwind utility classes:**
```typescript
// ✅ GOOD: Tailwind classes
function Card(): JSX.Element {
return (
<div className="rounded-lg border border-gray-200 bg-white p-4 shadow">
<h2 className="text-lg font-semibold">Title</h2>
</div>
);
}
// ✅ GOOD: Dynamic classes with clsx if needed
import { clsx } from "clsx";
function Badge({ variant }: { variant: "primary" | "secondary" }): JSX.Element {
return (
<span
className={clsx(
"px-3 py-1 rounded-full text-sm font-medium",
variant === "primary"
? "bg-blue-500 text-white"
: "bg-gray-100 text-gray-700",
)}
>
Badge
</span>
);
}
Common Pitfalls
❌ Removing manual optimizations when migrating to React Compiler
// BAD: Keep useCallback when migrating
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
// GOOD: Remove manual memoization
function handleClick() {
console.log("clicked");
}
❌ Stale closures in event handlers
// BAD: userId is captured at component creation
function UserCard({ userId }: { userId: string }) {
const handleDelete = () => {
deleteUser(userId); // Stale userId!
};
return <button onClick={handleDelete}>Delete</button>;
}
// GOOD: userId is passed when event fires
function UserCard({ userId }: { userId: string }): JSX.Element {
return (
<button onClick={() => deleteUser(userId)}>
Delete
</button>
);
}
❌ Missing ReactElement import (not needed!)
// BAD: Unnecessary import
import type { ReactElement } from "react";
function MyComponent(): ReactElement {
return <div>content</div>;
}
// GOOD: ReactElement is ambient (globally available)
function MyComponent() {
return <div>content</div>;
}
Deep Reference
For detailed technical reference on React Compiler behavior, hook patterns, state management with Zustand, Tailwind styling, and component composition patterns, see the reference guide.
Validation Commands
# Type check
npx tsc -b .
# Lint (includes React/Compiler rules)
npm run lint
# Unit tests
npm run test:unit
# Check for manual memoization (use sparingly)
grep -r "useMemo\|useCallback\|memo(" react/src --include="*.tsx"
References
- Reference guide: references/REFERENCE.md - Detailed React patterns
- Zustand patterns: references/ZUSTAND.md - Complete state management guide
- React Compiler docs: https://react.dev
- React Hooks guide: https://react.dev/reference/react/hooks
- TypeScript + React: React TypeScript Cheatsheet
- Project rules: .agent/rules.md
- TypeScript conventions: typescript-conventions skill
