
React Best Practices
- 3k installs
- 52 repo stars
- Updated June 24, 2026
- 0xbigboss/claude-code
react-best-practices is a skill that applies a React Effects decision tree so developers avoid redundant effects and use event handlers, memoization, and composition instead.
About
react-best-practices is a 0xbigboss/claude-code skill that guides React component work with a decision tree for when Effects are appropriate versus event handlers, render-time calculation, useMemo, key-based resets, refs, and useEffectEvent. The core principle treats Effects as escape hatches for synchronizing with external systems such as WebSocket, IntersectionObserver, third-party libraries, and window listeners, not for derived state or user-event responses. It documents anti-patterns including storing derived values in useEffect, effect chains, notifying parents from effects, and reading refs during render. Custom hook guidance prefers focused useXxx hooks that call other hooks, avoids lifecycle helper hooks, and keeps one concrete use case per hook. Component patterns cover controlled versus uncontrolled state, composition over prop drilling, compound components with provider-scoped state, and flushSync when synchronous DOM reads follow updates. The skill pairs with typescript-best-practices for typed React code and defers detailed examples to react-patterns.md. Developers reach for it when reviewing or authoring components that risk redundant effects and extra renders.
- Decision tree routes user events, derived state, memoization, keys, and external sync.
- Core rule: most component logic should not use Effects.
- Documents when to use refs, useEffectEvent, and custom hooks correctly.
- Warns against effect chains, derived state in effects, and ref reads during render.
- Pairs with typescript-best-practices and loads react-patterns.md for examples.
React Best Practices by the numbers
- 3,023 all-time installs (skills.sh)
- +21 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #182 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
react-best-practices capabilities & compatibility
- Capabilities
- route decisions between event handlers, render c · identify effect anti patterns and derived state · guide custom hook naming and composition pattern · reference react patterns.md for detailed code ex
What react-best-practices says it does
Most component logic should NOT use Effects.
npx skills add https://github.com/0xbigboss/claude-code --skill react-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3k |
|---|---|
| repo stars | ★ 52 |
| Security audit | 3 / 3 scanners passed |
| Last updated | June 24, 2026 |
| Repository | 0xbigboss/claude-code ↗ |
When should I use useEffect versus event handlers, useMemo, or the key prop in React components?
Apply React component patterns that minimize unnecessary Effects when reading or writing .tsx and .jsx files.
Who is it for?
Developers reading or writing React .tsx and .jsx components who need concrete guidance on Effects, refs, and hook design.
Skip if: Skip for non-React codebases or when you only need Next.js performance rules from a separate Vercel skill.
When should I use this skill?
User is reading or writing React components with .tsx or .jsx files and React imports.
What you get
Components structured with fewer unnecessary Effects, clearer event-handler updates, and patterns aligned to react-patterns.md examples.
- Component patterns aligned to Effect-minimizing rules
Files
React Best Practices
Pair with TypeScript
When working with React, always load both this skill and typescript-best-practices together. TypeScript patterns (type-first development, discriminated unions, Zod validation) apply to React code.
Core Principle: Effects Are Escape Hatches
Effects let you "step outside" React to synchronize with external systems. Most component logic should NOT use Effects. Before writing an Effect, ask: "Is there a way to do this without an Effect?"
Decision Tree
1. Need to respond to user interaction? Use event handler 2. Need computed value from props/state? Calculate during render 3. Need cached expensive calculation? Use useMemo 4. Need to reset state on prop change? Use key prop 5. Need to synchronize with external system? Use Effect with cleanup 6. Need non-reactive code in Effect? Use useEffectEvent 7. Need mutable value that doesn't trigger render? Use ref
When to Use Effects
Synchronizing with external systems: browser APIs (WebSocket, IntersectionObserver), third-party non-React libraries, window/document event listeners, non-React DOM elements (video, maps).
When NOT to Use Effects
- Derived state — calculate during render
- Expensive calculations — use
useMemo - Resetting state on prop change — use
keyprop - Responding to user events — use event handlers
- Notifying parent of state changes — update both in the same event handler
- Chains of effects — calculate derived state and update in one event handler
Refs
- Use for values that don't affect rendering (timer IDs, DOM node references)
- Never read or write
ref.currentduring render; only in event handlers and effects - Use ref callbacks (not
useRefin loops) for dynamic lists - Use
useImperativeHandleto limit what parent can access
Custom Hooks
- Share logic, not state — each call gets an independent state instance
- Name
useXxxonly if it actually calls other hooks; otherwise use a regular function - Avoid lifecycle hooks (
useMount,useEffectOnce) — useuseEffectdirectly so the linter catches missing deps - Keep focused on a single concrete use case
Component Patterns
- Controlled: parent owns state; uncontrolled: component owns state
- Prefer composition with
childrenover prop drilling - Treat boolean props that switch large component trees (
isEditing,isThread,hideAttachments) as a composition smell; prefer separate composed components for distinct use cases - For complex reusable UI, prefer compound components with provider-scoped state/actions over monolithic components with many optional props
- Use Context for scoped component families as well as truly global state, when it defines a local interface consumed by descendants
- Render JSX directly for UI variation; avoid config-array mini-frameworks unless the config is real domain data
- Lift the provider boundary when sibling or external controls need access to the same state/actions
- Use
flushSyncwhen you need to read the DOM synchronously after a state update
See react-patterns.md for code examples and detailed patterns.
React Patterns Reference
Code examples for patterns summarized in SKILL.md. Load this file when you need to see or produce a concrete implementation.
Effect Anti-Patterns
Derived State (Calculate During Render)
// BAD: Effect for derived state
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// GOOD: Calculate during render
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const fullName = firstName + ' ' + lastName;Expensive Calculations (Use useMemo)
// BAD: Effect for caching
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
setVisibleTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);
// GOOD: useMemo for expensive calculations
const visibleTodos = useMemo(
() => getFilteredTodos(todos, filter),
[todos, filter]
);Resetting State on Prop Change (Use key)
// BAD: Effect to reset state
function ProfilePage({ userId }) {
const [comment, setComment] = useState('');
useEffect(() => {
setComment('');
}, [userId]);
}
// GOOD: Use key to reset component state
function ProfilePage({ userId }) {
return <Profile userId={userId} key={userId} />;
}
function Profile({ userId }) {
const [comment, setComment] = useState(''); // Resets automatically when key changes
}User Event Handling (Use Event Handlers)
// BAD: Event-specific logic in Effect
function ProductPage({ product, addToCart }) {
useEffect(() => {
if (product.isInCart) {
showNotification(`Added ${product.name} to cart`);
}
}, [product]);
}
// GOOD: Logic in event handler
function ProductPage({ product, addToCart }) {
function buyProduct() {
addToCart(product);
showNotification(`Added ${product.name} to cart`);
}
}Notifying Parent of State Changes
// BAD: Effect to notify parent
function Toggle({ onChange }) {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
onChange(isOn);
}, [isOn, onChange]);
}
// GOOD: Update both in event handler
function Toggle({ onChange }) {
const [isOn, setIsOn] = useState(false);
function updateToggle(nextIsOn) {
setIsOn(nextIsOn);
onChange(nextIsOn);
}
}
// BEST: Fully controlled component
function Toggle({ isOn, onChange }) {
function handleClick() {
onChange(!isOn);
}
}Chains of Effects
// BAD: Effect chain — each effect re-renders before the next fires
useEffect(() => {
if (card !== null && card.gold) {
setGoldCardCount(c => c + 1);
}
}, [card]);
useEffect(() => {
if (goldCardCount > 3) {
setRound(r => r + 1);
setGoldCardCount(0);
}
}, [goldCardCount]);
// GOOD: Calculate derived state, update everything in one event handler
const isGameOver = round > 5;
function handlePlaceCard(nextCard) {
setCard(nextCard);
if (nextCard.gold) {
if (goldCardCount < 3) {
setGoldCardCount(goldCardCount + 1);
} else {
setGoldCardCount(0);
setRound(round + 1);
}
}
}Effect Dependencies
Never Suppress the Linter
// BAD: Suppressing linter hides bugs
useEffect(() => {
const id = setInterval(() => {
setCount(count + increment);
}, 1000);
return () => clearInterval(id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// GOOD: Fix the code, not the linter
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + increment);
}, 1000);
return () => clearInterval(id);
}, [increment]);Use Updater Functions to Remove State Dependencies
// BAD: messages in dependencies causes reconnection on every message
useEffect(() => {
connection.on('message', (msg) => {
setMessages([...messages, msg]);
});
}, [messages]); // Reconnects on every message!
// GOOD: Updater function removes the dependency
useEffect(() => {
connection.on('message', (msg) => {
setMessages(msgs => [...msgs, msg]);
});
}, []); // No messages dependency neededMove Objects/Functions Inside Effects
// BAD: Object created each render triggers Effect
function ChatRoom({ roomId }) {
const options = { serverUrl, roomId }; // New object each render
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // Reconnects every render!
}
// GOOD: Create object inside Effect
function ChatRoom({ roomId }) {
useEffect(() => {
const options = { serverUrl, roomId };
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]); // Only reconnects when values change
}useEffectEvent for Non-Reactive Logic
// BAD: theme change reconnects chat
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected!', theme);
});
connection.connect();
return () => connection.disconnect();
}, [roomId, theme]); // Reconnects on theme change!
}
// GOOD: useEffectEvent for non-reactive logic
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]); // theme no longer causes reconnection
}Wrap Callback Props with useEffectEvent
// BAD: Callback prop in dependencies reconnects if parent re-renders
function ChatRoom({ roomId, onReceiveMessage }) {
useEffect(() => {
connection.on('message', onReceiveMessage);
}, [roomId, onReceiveMessage]);
}
// GOOD: Wrap callback in useEffectEvent
function ChatRoom({ roomId, onReceiveMessage }) {
const onMessage = useEffectEvent(onReceiveMessage);
useEffect(() => {
connection.on('message', onMessage);
}, [roomId]); // Stable dependency list
}Effect Cleanup
Always Clean Up Subscriptions
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect(); // REQUIRED
}, [roomId]);
useEffect(() => {
function handleScroll(e) {
console.log(window.scrollY);
}
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll); // REQUIRED
}, []);Data Fetching with Ignore Flag
useEffect(() => {
let ignore = false;
async function fetchData() {
const result = await fetchTodos(userId);
if (!ignore) {
setTodos(result);
}
}
fetchData();
return () => {
ignore = true; // Prevents stale data from superseded requests
};
}, [userId]);Development Double-Fire Is Intentional
React remounts components in development to verify cleanup works. If effects fire twice, fix the cleanup — don't suppress the double-fire:
// BAD: Hiding the symptom
const didInit = useRef(false);
useEffect(() => {
if (didInit.current) return;
didInit.current = true;
// ...
}, []);
// GOOD: Fix the cleanup so remounting is safe
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, []);Ref Patterns
Use Refs for Values That Don't Affect Rendering
// GOOD: Ref for timeout ID (doesn't affect UI)
const timeoutRef = useRef(null);
function handleClick() {
clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
// ...
}, 1000);
}
// BAD: Using ref for displayed value — UI won't update
const countRef = useRef(0);
countRef.current++;Never Read/Write ref.current During Render
// BAD: Reading/writing ref during render
function MyComponent() {
const ref = useRef(0);
ref.current++; // Mutating during render!
return <div>{ref.current}</div>; // Reading during render!
}
// GOOD: Read/write refs in event handlers and effects
function MyComponent() {
const ref = useRef(0);
function handleClick() {
ref.current++; // OK in event handler
}
useEffect(() => {
ref.current = someValue; // OK in effect
}, [someValue]);
}Ref Callbacks for Dynamic Lists
// BAD: Can't call useRef in a loop
{items.map((item) => {
const ref = useRef(null); // Rules of Hooks violation!
return <li ref={ref} />;
})}
// GOOD: Ref callback with Map
const itemsRef = useRef(new Map());
{items.map((item) => (
<li
key={item.id}
ref={(node) => {
if (node) {
itemsRef.current.set(item.id, node);
} else {
itemsRef.current.delete(item.id);
}
}}
/>
))}useImperativeHandle for Controlled Exposure
// Limit what parent can access through a ref — expose only the API surface you intend
function MyInput({ ref }) {
const realInputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus() {
realInputRef.current.focus();
},
// Parent can ONLY call focus(), not access the full DOM node
}));
return <input ref={realInputRef} />;
}Custom Hook Patterns
Hooks Share Logic, Not State
// Each call gets independent state — these are two separate online status subscriptions
function StatusBar() {
const isOnline = useOnlineStatus();
}
function SaveButton() {
const isOnline = useOnlineStatus();
}Name Hooks useXxx Only If They Use Hooks
// BAD: useXxx prefix but doesn't call any hooks
function useSorted(items) {
return items.slice().sort();
}
// GOOD: Regular function
function getSorted(items) {
return items.slice().sort();
}
// GOOD: Uses hooks, so prefix with use
function useAuth() {
return useContext(AuthContext);
}Avoid "Lifecycle" Hooks
// BAD: Custom lifecycle hooks prevent linter from catching missing dependencies
function useMount(fn) {
useEffect(() => {
fn();
}, []); // fn is missing from dependencies — linter can't catch it
}
// GOOD: Use useEffect directly
useEffect(() => {
doSomething();
}, [doSomething]);Component Patterns
Controlled vs Uncontrolled
// Uncontrolled: component owns state
function SearchInput() {
const [query, setQuery] = useState('');
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}
// Controlled: parent owns state — more composable, easier to test
function SearchInput({ query, onQueryChange }) {
return <input value={query} onChange={e => onQueryChange(e.target.value)} />;
}Prefer Composition Over Prop Drilling
// BAD: Prop drilling through intermediate components that don't use the value
<App user={user}>
<Layout user={user}>
<Header user={user}>
<Avatar user={user} />
</Header>
</Layout>
</App>
// GOOD: Pass the rendered element, not raw data
<App>
<Layout>
<Header avatar={<Avatar user={user} />} />
</Layout>
</App>
// GOOD: Context for truly global state (auth, theme, locale)
<UserContext.Provider value={user}>
<App />
</UserContext.Provider>flushSync for Synchronous DOM Updates
// When you need to read the DOM immediately after a state update
// (e.g., scroll to a newly added list item before the next paint)
import { flushSync } from 'react-dom';
function handleAdd() {
flushSync(() => {
setTodos([...todos, newTodo]);
});
// DOM is now updated synchronously — safe to read layout
listRef.current.lastChild.scrollIntoView();
}Related skills
How it compares
Use react-best-practices for React-specific hook and effect patterns; use a broader frontend-design skill when the task is visual UI composition rather than hooks correctness.
FAQ
When should I use useEffect in React?
Only to synchronize with external systems like browser APIs, third-party libraries, or non-React DOM elements, with cleanup when needed.
How do I reset state when a prop changes?
Use the key prop on the component rather than an Effect that copies props into state.
Does react-best-practices include code examples?
SKILL.md summarizes patterns; react-patterns.md holds detailed before-and-after examples.
Is React Best Practices safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.