Skip to content
Development
Skill

/react

Apply when writing React components. Hook discipline, state placement, performance, async cleanup, and list keys.

From plugin
skill-everything
2123 skills

Claude Code

$ npx -y skills add sordi-ai/skill-everything --skill react --agent claude-code

SKILL.md

react.SKILL.md
name: react
description: Apply when writing React components. Hook discipline, state placement, performance, async cleanup, and list keys.
license: MIT
version: 1.1.0
tokens_target: 3000
triggers:
  - react component
  - react hooks
  - react performance
loads_after:
  - code-quality
  - typescript
supersedes: []

Sub-Skill: React Best Practices

<!-- target: ~2600 tokens (real tiktoken count) | 17 rules with severity classification -->

**Purpose:** Prevents the React-specific mistakes LLMs make repeatedly — wrong state placement, stale closures, unnecessary re-renders, and broken async patterns. Concrete rules with code examples.

Rule classification

  • **MUST** — load-bearing. Violating causes infinite loops, stale data, leaked subscriptions, or invisible UI bugs. Never break.
  • **SHOULD** — default behavior. Deviation needs a documented reason in the code or PR.
  • **AVOID** — usually wrong; documented exception inline where needed.

**Where these rules don't strictly apply:** test fixtures, Storybook stories, design-system primitives in isolation, and small in-tutorial demo components may legitimately differ. The rules below apply to **production application code**.

---

Component Design

1. **SHOULD: Co-locate state with the component that owns it.** Lift state only when two siblings genuinely share it. Lifting to a grandparent "just in case" causes unnecessary re-renders across the tree.

   // Avoid: form state lifted to page-level parent
   function Page() {
     const [email, setEmail] = useState('');
     return <Form email={email} setEmail={setEmail} />;
   }

   // Prefer: state lives in the component that uses it
   function Form() {
     const [email, setEmail] = useState('');
     return <input value={email} onChange={e => setEmail(e.target.value)} />;
   }

2. **SHOULD: Split components at ~100 lines or when a section has its own data concern.** One component = one responsibility. Extract `<UserAvatar>`, `<OrderSummary>` rather than one `<ProfilePage>` that does everything. *Exception: pages that are mostly markup with little logic may exceed 100 lines.*

3. **SHOULD: Replace prop drilling beyond two levels with composition or context.** Passing `userId` through four components to reach a button is a design smell.

   // Avoid: drilling through intermediaries
   <Layout userId={userId}><Sidebar userId={userId}><Nav userId={userId} /></Sidebar></Layout>

   // Prefer: context or render-prop composition
   <UserContext.Provider value={userId}><Layout /></UserContext.Provider>

4. **MUST: Never create component definitions inside render.** Inner components are recreated on every render, destroying their state and forcing full remounts.

   // Wrong
   function Parent() {
     const Child = () => <div>hello</div>; // new reference every render
     return <Child />;
   }

   // Correct: define outside
   const Child = () => <div>hello</div>;
   function Parent() { return <Child />; }

---

State Management

5. **MUST: Never mutate state directly.** React compares references. Mutating in place skips re-renders silently.

   // Wrong
   const [items, setItems] = useState([]);
   items.push(newItem); // mutation — React does not re-render
   setItems(items);

   // Correct
   setItems(prev => [...prev, newItem]);

6. **SHOULD: Compute derived values in render, not in useEffect.** If a value can be calculated from existing state/props, calculate it inline. useEffect for derived state creates a one-render lag and extra state variables.

   // Avoid
   const [fullName, setFullName] = useState('');
   useEffect(() => { setFullName(`${first} ${last}`); }, [first, last]);

   // Prefer
   const fullName = `${first} ${last}`;

7. **MUST: Use useRef for values that must not trigger re-renders** (timers, DOM nodes, previous values). Use useState for anything the UI depends on. Mixing them causes invisible bugs.

   // Wrong: ref for displayed value
   const count = useRef(0);
   count.current++; // UI never updates

   // Wrong: state for a timer ID
   const [timerId, setTimerId] = useState(null); // triggers re-render on set

   // Correct
   const timerId = useRef(null);

---

Hooks

8. **MUST: Specify complete dependency arrays in useEffect.** Omitting a dependency creates a stale closure. The ESLint rule `exhaustive-deps` must be enabled and respected.

   // Wrong: stale closure over userId
   useEffect(() => { fetchUser(userId); }, []); // runs once, userId never updates

   // Correct
   useEffect(() => { fetchUser(userId); }, [userId]);

9. **MUST: Cancel async operations in useEffect cleanup.** Fetch without an AbortController causes state updates on unmounted components and race conditions.

   useEffect(() => {
     const controller = new AbortController();
     fetch(`/api/user/${id}`, { signal: controller.signal })
       .then(r => r.json())
       .then(setUser)
       .catch(err => { if (err.name !== 'AbortError') setError(err); });
     return () => controller.abort();
   }, [id]);

10. **MUST: Never call hooks conditionally or inside loops.** Hook call order must be identical on every render. Wrap conditional logic inside the hook body, not around the hook call.

    // Wrong
    if (isLoggedIn) { const user = useUser(); }

    // Correct
    const user = useUser(); // hook always called; handle null inside

---

Performance

11. **SHOULD: Wrap expensive computations in useMemo, not inline.** Recalculating a sorted/filtered list on every render is the most common performance bug in React. *Exception: small lists (<20 items) where the recompute is negligible — adding useMemo costs more than it saves.*

    // Avoid: re-sorts on every render including unrelated state changes
    const sorted = items.sort((a, b) => a.name.localeCompare(b.name));

    // Prefer
    const sorted = useMem
Read more
@sordi-ai
Ships withskill-everything

Git-versioned agent memory: agents that never make the same mistake twice. Anthropic-Skill folder standard, multi-runtime (Claude Code, Cursor, Gemini CLI, OpenCode).

Stars on GitHub

Momentum

stars in total
21
Star history of sordi-ai/skill-everything on GitHub, a chart by star-history.com

Other skills on skill-everything.

code-quality

code-quality

Apply when writing or refactoring code. Generic rules to prevent the most common review comments — function length, naming, error…

db-schema

db-schema

Apply when designing database schemas, writing migrations, or reviewing table structure. Covers naming, keys, indexes,…

debugging

debugging

Apply when diagnosing a bug, reproducing a failure, or performing root cause analysis. Covers systematic isolation, binary…

docker

docker

Apply when writing or reviewing Dockerfiles, docker compose files, or container build pipelines. Covers layer caching,…

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills2w309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react5d252k
@facebook@facebookInstall Skill

Questions about react.

Which coding agents can use react?
react installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.