04-anti-patterns
React anti-patterns to avoid — manual JSX repetition, useEffect misuse, reference instability, prop drilling, conditional hooks, and common performance traps
- License 许可
- MIT
- 最近更新
- 2026-05-31
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- 04-anti-patterns
- license
- MIT
- description
- React anti-patterns to avoid — manual JSX repetition, useEffect misuse, reference instability, prop drilling, conditional hooks, and common performance traps
- compatibility
- opencode
03 SKILL.md 正文
React Anti-Patterns
Common patterns that cause bugs, performance issues, or maintenance problems in React.
Manual JSX Repetition
NEVER repeat JSX elements manually. Always render from data with Array.map.
// ❌ MANUAL — violates DRY, unmaintainable, error-prone
<div>
<TildeLine />
<TildeLine />
<TildeLine />
<TildeLine />
<TildeLine />
<TildeLine />
<TildeLine />
</div>
// ✅ DATA-DRIVEN — declarative, scalable, maintainable
<div>
{Array.from({ length: 7 }, (_, i) => (
<TildeLine key={i} />
))}
</div>
// ✅ with actual data
<div>
{lines.map((line) => (
<TildeLine key={line.id} variant={line.variant} />
))}
</div>
Why this matters:
- Manual repetition hides intent (why 7?)
- Changing count requires editing JSX, not data
- No way to add per-item props without more repetition
- Violates FP principle: derive UI from data, don't hardcode structure
useEffect for Derived State
Never use useEffect to synchronize state from props or other state.
// ❌ SYNC STATE — infinite loops, stale data, complexity
const [sortedItems, setSortedItems] = useState(items);
useEffect(() => {
setSortedItems([...items].sort());
}, [items]);
// ✅ DERIVE — pure, synchronous, no extra renders
const sortedItems = useMemo(() => [...items].sort(), [items]);
// ✅ even simpler — inline if cheap
const sortedItems = [...items].sort();
// ❌ SYNC PROPS TO STATE — anti-pattern
const [value, setValue] = useState(initialValue);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
// ✅ CONTROLLED — parent owns state
function Input({ value, onChange }: { value: string; onChange: (v: string) => void }) {
return <input value={value} onChange={(e) => onChange(e.target.value)} />;
}
// ✅ UNCONTROLLED with key reset
<input key={resetKey} defaultValue={initialValue} />
Reference Instability in JSX
Never create objects, arrays, or functions inline in JSX props.
// ❌ NEW REFERENCE EVERY RENDER — child re-renders unnecessarily
<Component
style={{ color: "red", fontSize: 14 }}
config={{ enabled: true, mode: "auto" }}
items={["a", "b", "c"]}
onClick={() => handleClick(id)}
/>
// ✅ STABLE REFERENCES — memoize or lift out
const style = { color: "red", fontSize: 14 };
const config = { enabled: true, mode: "auto" };
const items = ["a", "b", "c"];
const handleItemClick = useCallback((id: string) => {
handleClick(id);
}, [handleClick]);
<Component
style={style}
config={config}
items={items}
onClick={handleItemClick}
/>
// ❌ inline arrow in JSX — new function every render
<button onClick={() => setCount((c) => c + 1)}>Increment</button>
// ✅ stable handler
const increment = () => setCount((c) => c + 1);
<button onClick={increment}>Increment</button>
useCallback / useMemo Misuse
Don't memoize everything. Don't forget dependencies.
// ❌ memoizing cheap operations — overhead > benefit
const count = useMemo(() => items.length, [items]);
const isEmpty = useMemo(() => items.length === 0, [items]);
// ✅ just compute inline
const count = items.length;
const isEmpty = items.length === 0;
// ❌ missing dependencies — stale closures
const handleSubmit = useCallback(() => {
submit(formData); // formData is stale!
}, []); // forgot formData
// ✅ correct dependencies
const handleSubmit = useCallback(() => {
submit(formData);
}, [formData]);
// ❌ useCallback without memoized child — no benefit
const handleClick = useCallback(() => {}, []);
<button onClick={handleClick}>Click</button>; // button is not memoized
// ✅ only when child is wrapped in React.memo
const MemoizedChild = React.memo(Child);
<MemoizedChild onClick={handleClick} />;
Prop Drilling
Don't thread props through multiple component layers.
// ❌ PROP DRILLING — App → Layout → Header → UserMenu → Avatar
function App() {
const user = useUser();
return <Layout user={user} />;
}
function Layout({ user }: { user: User }) {
return <Header user={user} />;
}
function Header({ user }: { user: User }) {
return <UserMenu user={user} />;
}
// ✅ CONTEXT — intermediate components don't know about user
const UserContext = createContext<User | null>(null);
function App() {
const user = useUser();
return (
<UserContext.Provider value={user}>
<Layout />
</UserContext.Provider>
);
}
function Avatar() {
const user = useContext(UserContext); // only consumer knows
return <img src={user?.avatar} />;
}
// ✅ COMPOSITION — pass children instead of props
<Layout header={<UserMenu />} />
useEffect without Cleanup
Always clean up side effects.
// ❌ NO CLEANUP — memory leaks, stale subscriptions
useEffect(() => {
const subscription = source.subscribe(setData);
}, [source]);
// ✅ CLEANUP
useEffect(() => {
const subscription = source.subscribe(setData);
return () => subscription.unsubscribe();
}, [source]);
// ❌ NO CLEANUP — event listener leaks
useEffect(() => {
window.addEventListener("resize", handleResize);
}, []);
// ✅ CLEANUP
useEffect(() => {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
Conditional Hooks
Never call hooks conditionally or in loops.
// ❌ CONDITIONAL HOOK — breaks Rules of Hooks
if (isLoggedIn) {
const user = useUser(); // crash or undefined behavior
}
// ✅ CONDITIONAL RENDER — hooks always run
const user = useUser();
if (!isLoggedIn) return <Login />;
return <Dashboard user={user} />;
// ❌ HOOK IN LOOP — breaks Rules of Hooks
{items.map((item) => {
const ref = useRef(null); // crash!
return <div ref={ref}>{item.name}</div>;
})}
// ✅ SINGLE HOOK WITH ARRAY
const refs = useRef<(HTMLDivElement | null)[]>([]);
{items.map((item, i) => (
<div ref={(el) => { refs.current[i] = el; }}>{item.name}</div>
))}
Large Components
Split components that exceed ~100 lines or mix concerns.
// ❌ GOD COMPONENT — 300+ lines, mixed data, UI, logic
function Dashboard() {
const [users, setUsers] = useState([]);
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
// ... 200 more lines of handlers, effects, JSX
}
// ✅ SEPARATED BY CONCERN
function Dashboard() {
return (
<PageLayout>
<UserSection />
<PostSection />
</PageLayout>
);
}
function UserSection() {
const { users, isLoading } = useUsers();
if (isLoading) return <Loading />;
return <UserList users={users} />;
}
function PostSection() {
const { posts, isLoading } = usePosts();
if (isLoading) return <Loading />;
return <PostList posts={posts} />;
}
Direct State Mutation
Never mutate state directly — React won't detect changes.
// ❌ MUTATION — React can't detect, won't re-render
const handleAdd = () => {
items.push(newItem);
setItems(items);
};
// ✅ NEW ARRAY
const handleAdd = () => {
setItems([...items, newItem]);
};
// ❌ MUTATION — nested object
const handleUpdate = () => {
user.name = "Alice";
setUser(user);
};
// ✅ NEW OBJECT
const handleUpdate = () => {
setUser({ ...user, name: "Alice" });
};
// ❌ MUTATION — array element
items[0].completed = true;
setItems(items);
// ✅ MAP
setItems(items.map((item, i) =>
i === 0 ? { ...item, completed: true } : item
));
Inline Function Definitions
Avoid creating functions inside JSX render body.
// ❌ INLINE IN JSX — new function every render, reference instability
<div>
{items.map((item) => (
<button onClick={() => handleItemClick(item.id)}>
{item.name}
</button>
))}
</div>
// ✅ STABLE HANDLER FACTORY
const makeHandleClick = (id: string) => () => handleItemClick(id);
<div>
{items.map((item) => (
<button key={item.id} onClick={makeHandleClick(item.id)}>
{item.name}
</button>
))}
</div>
// ✅ OR CURRIED HANDLER
const handleItemClick = (id: string) => () => {
// handle click
};
<div>
{items.map((item) => (
<button key={item.id} onClick={handleItemClick(item.id)}>
{item.name}
</button>
))}
</div>
Default Values in Destructuring
Don't use destructuring defaults for optional props — hides missing prop bugs.
// ❌ HIDDEN DEFAULT — caller might not know prop exists
function Button({ variant = "primary" }: { variant?: string }) {
return <button className={variant}>Click</button>;
}
// ✅ EXPLICIT DEFAULT — visible in component body
function Button({ variant }: { variant?: string }) {
const resolvedVariant = variant ?? "primary";
return <button className={resolvedVariant}>Click</button>;
}
// ✅ REQUIRED PROP — fail fast
function Button({ variant }: { variant: string }) {
return <button className={variant}>Click</button>;
}
Summary
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Manual JSX repetition | DRY violation, unmaintainable | Array.map over data |
| useEffect for derived state | Extra renders, complexity | useMemo or inline derivation |
| Inline objects/arrays in JSX | Reference instability, re-renders | Lift out, memoize, or use constants |
| useCallback/useMemo misuse | Unnecessary overhead, stale closures | Only for expensive ops / memoized children |
| Prop drilling | Tight coupling, refactoring pain | Context API or composition |
| useEffect without cleanup | Memory leaks, stale subscriptions | Always return cleanup function |
| Conditional hooks | Undefined behavior, crashes | Conditional render, not conditional hooks |
| Large components | Mixed concerns, hard to test | Split by data boundary or concern |
| Direct state mutation | Missed re-renders, bugs | Spread, map, filter for immutability |
| Inline functions in JSX | Reference instability | Factory functions or curried handlers |
| Destructuring defaults | Hidden bugs, unclear API | Explicit defaults or required props |
04 同仓库更多 Skills
tmux-workflow
Development workflow with tmux — run dev servers, Storybook, and background processes in persistent sessions with live logs, while keeping OpenCode free for chat
03-state-management
React state management patterns — lifting state, colocation, URL state, server state with TanStack Query, local state with useState/useReducer
02-component-composition
React component composition patterns — render props, compound components, list rendering with map, slot patterns, and layout primitives
01-fp-style
React functional programming style — pure components, hooks conventions, Effect Atom integration, and Tailwind patterns
react-animation
React animation with react/motion (Framer Motion) — declarative animations, gestures, layout animations, and performance best practices
04-bun-test
Bun built-in test runner — zero-config testing for Effect, React, and TypeScript projects