react-development

Structured guidance for functional React development with Hooks. Covers dependency arrays, state lifting decisions, custom hooks patterns, and performance optimization trade-offs.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "react-development" with this command: npx skills add jacobporci/skills/jacobporci-skills-react-development

React Development

Structured guidance for functional React development with Hooks. Covers dependency arrays, state lifting decisions, custom hooks patterns, and performance optimization trade-offs.

When to Apply

  • Building or refactoring functional components with Hooks

  • Deciding between local state, lifted state, or Context API

  • Optimizing components for performance

  • Debugging unnecessary re-renders

  • Creating reusable logic with custom hooks

  • Implementing lazy loading or code splitting strategies

Rule Categories by Priority

Priority Category Impact Focus

1 Dependency Arrays HIGH Prevent stale closures and infinite loops

2 State Lifting HIGH Choose correct state owner and minimize prop drilling

3 Custom Hooks HIGH Extract stateful logic for reusability

4 Context Usage MEDIUM Balance Context depth vs component coupling

5 Callback Optimization MEDIUM useCallback trade-offs vs re-render costs

6 Memoization Strategy MEDIUM React.memo / useMemo effectiveness

7 Lazy Loading MEDIUM Code splitting and dynamic imports

Quick Reference

  1. Dependency Arrays (HIGH)
  • hooks-dependency-array-completeness

  • Include all values that change

  • hooks-avoid-stale-closures

  • Prevent stale state in event handlers

  • hooks-avoid-infinite-loops

  • Detect circular dependencies

  1. State Lifting (HIGH)
  • state-lift-shared-state

  • Move state to lowest common parent

  • state-context-decision

  • When to use Context vs prop drilling

  1. Custom Hooks (HIGH)
  • hooks-extract-stateful-logic
  • Create custom hooks for reusable logic
  1. Optimization (MEDIUM)
  • perf-memoization-trade-offs

  • When React.memo is worth the cost

  • perf-lazy-load-components

  • Code splitting patterns

Dependency Arrays (HIGH Impact)

Incomplete Dependency Array

Explanation: Missing dependencies in useEffect cause stale closures where the effect runs with outdated variable values. This leads to bugs that only surface when specific variable changes don't trigger expected updates.

Incorrect:

function Timer({ delay }) { const [count, setCount] = useState(0);

useEffect(() => { // delay changes, but effect doesn't re-run → timer uses stale delay const timer = setInterval(() => setCount(c => c + 1), delay); return () => clearInterval(timer); }, []); // Missing 'delay' — will always use initial value

return <div>{count}</div>; }

Correct:

function Timer({ delay }) { const [count, setCount] = useState(0);

useEffect(() => { const timer = setInterval(() => setCount(c => c + 1), delay); return () => clearInterval(timer); }, [delay]); // Re-run when delay changes

return <div>{count}</div>; }

Decision: Include every variable from the component scope that is referenced inside useEffect. Use ESLint exhaustive-deps rule.

State Lifting Decision (HIGH Impact)

Explanation: Senior teams must distinguish between lifting state one level (to avoid prop drilling) vs. jumping to Context API. Over-lifting creates tight coupling; under-lifting creates deep prop chains. The decision depends on consumer count and intermediate component count.

Incorrect:

// Over-lifting to Context too early (fragile to component tree changes) const UserContext = createContext();

export function Layout() { const [user, setUser] = useState(null); return ( <UserContext.Provider value={{ user, setUser }}> <Header /> <Content /> <Footer /> </UserContext.Provider> ); }

function Content() { return <Page />; // Intermediate component unaware of context }

function Page() { const { user } = useContext(UserContext); // Fine until design changes }

Correct:

// Lift state to parent, pass down explicitly to consumers export function Layout() { const [user, setUser] = useState(null); return ( <> <Header user={user} /> <Content user={user} setUser={setUser} /> <Footer user={user} /> </> ); }

// Only use Context when 3+ levels of intermediates or 5+ consumers const UserContext = createContext(); export function UserProvider({ children }) { const [user, setUser] = useState(null); return ( <UserContext.Provider value={{ user, setUser }}> {children} </UserContext.Provider> ); }

Decision Rule: If ≤2 intermediate components and <5 consumers, lift state. If 3+ intermediates OR 5+ consumers, use Context.

Custom Hooks for Logic Reuse (HIGH Impact)

Explanation: Senior developers avoid copy-pasting hooks logic across components. Extract to custom hooks when the same state + effects pattern repeats. Custom hooks improve testability, reduce component complexity, and create a reusable vocabulary.

Incorrect:

function SearchUsers() { const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false);

useEffect(() => { if (!query) return; setLoading(true); fetch(/api/users?q=${query}) .then(r => r.json()) .then(data => { setResults(data); setLoading(false); }); }, [query]);

return <div>{loading ? "..." : results.map(u => <div key={u.id}>{u.name}</div>)}</div>; }

// Duplicated in another component function SearchPosts() { const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false);

useEffect(() => { if (!query) return; setLoading(true); fetch(/api/posts?q=${query}) .then(r => r.json()) .then(data => { setResults(data); setLoading(false); }); }, [query]);

return <div>{loading ? "..." : results.map(p => <div key={p.id}>{p.title}</div>)}</div>; }

Correct:

function useSearch(endpoint) { const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false);

useEffect(() => { if (!query) return; setLoading(true); fetch(/api/${endpoint}?q=${query}) .then(r => r.json()) .then(data => { setResults(data); setLoading(false); }); }, [query, endpoint]);

return { query, setQuery, results, loading }; }

function SearchUsers() { const { query, setQuery, results, loading } = useSearch("users"); return <div>{loading ? "..." : results.map(u => <div key={u.id}>{u.name}</div>)}</div>; }

function SearchPosts() { const { query, setQuery, results, loading } = useSearch("posts"); return <div>{loading ? "..." : results.map(p => <div key={p.id}>{p.title}</div>)}</div>; }

Decision: Extract when same pattern appears in 2+ components or when logic exceeds 10 lines.

Context API Trade-offs (MEDIUM Impact)

Explanation: Context is a global state broadcast—every consumer re-renders when any value changes. For fine-grained state (user object, theme), this is acceptable. For high-frequency updates (form input, scroll position), Context causes performance regressions. Use Zustand, Jotai, or reducer for latter.

Incorrect:

// Context broadcasts full form state on every keystroke const FormContext = createContext();

export function FormProvider({ children }) { const [formData, setFormData] = useState({ name: "", email: "", message: "" }); const handleChange = (field, value) => setFormData(prev => ({ ...prev, [field]: value }));

return ( <FormContext.Provider value={{ formData, handleChange }}> {children} </FormContext.Provider> ); }

function NameInput() { const { formData, handleChange } = useContext(FormContext); // Re-renders on email or message changes too — wasteful return <input value={formData.name} onChange={e => handleChange("name", e.target.value)} />; }

Correct:

// Use useReducer for form state—batches updates, better for complex forms function useFormState(initialState) { const [formData, dispatch] = useReducer((state, action) => { return { ...state, [action.field]: action.value }; }, initialState);

const handleChange = (field, value) => dispatch({ field, value }); return { formData, handleChange }; }

function FormComponent() { const { formData, handleChange } = useFormState({ name: "", email: "", message: "" }); return ( <> <input value={formData.name} onChange={e => handleChange("name", e.target.value)} /> <input value={formData.email} onChange={e => handleChange("email", e.target.value)} /> </> ); }

Decision: Use Context for infrequent state (user, theme, auth). Use useState/useReducer for form input; use external store (Zustand/Jotai) for high-frequency updates.

Memoization Cost–Benefit (MEDIUM Impact)

Explanation: React.memo and useMemo prevent re-renders but add memory and comparison overhead. Overuse defeats purpose. Profile first; only memoize if component re-renders excessively AND re-render cost exceeds memoization cost.

Incorrect:

// Memoizing cheap component — comparison overhead > benefit const SmallButton = React.memo(({ onClick, label }) => { return <button onClick={onClick}>{label}</button>; });

// Every render still creates new function — memo ineffective function Parent() { return <SmallButton onClick={() => console.log("click")} label="Click" />; }

Correct:

// Only memoize expensive component const ExpensiveList = React.memo(({ items, onItemClick }) => { return items.map(item => ( <div key={item.id} onClick={() => onItemClick(item.id)}> {item.name} </div> )); }, (prev, next) => prev.items === next.items && prev.onItemClick === next.onItemClick);

function Parent() { const handleItemClick = useCallback(id => console.log(id), []); const items = useMemo(() => largeArray, [largeArray]);

return <ExpensiveList items={items} onItemClick={handleItemClick} />; }

Decision: Only memoize if DevTools shows 3+ re-renders per interaction AND component renders in <10ms without memo. Always pair React.memo with useCallback for props.

How to Use

Step 1: Identify the problem

Determine which category applies: dependency arrays (stale state), state lifting (prop drilling), custom hooks (code duplication), Context usage (performance), or memoization (re-renders).

Step 2: Consult the rule

Read the Incorrect example to recognize the anti-pattern. Check the Decision criteria.

Step 3: Apply the pattern

Implement the Correct example, adapting to your codebase. Use ESLint rules to enforce (e.g., exhaustive-deps , no-const-assign ).

Step 4: Validate

Test with React DevTools Profiler to confirm re-render reduction. Measure first, optimize second.

References

  • React Hooks Rules

  • useEffect Dependency Array

  • useContext Performance

  • React.memo

  • Custom Hooks

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.

Repository SourceNeeds Review
94.2K159.5K
anthropics
Coding

remotion-best-practices

Use this skills whenever you are dealing with Remotion code to obtain the domain-specific knowledge.

Repository SourceNeeds Review
2.1K147.4K
remotion-dev
Coding

azure-ai

Service Use When MCP Tools CLI

Repository SourceNeeds Review
155135.8K
microsoft