
No Use Effect
Write and review React UI without defaulting to useEffect—use derived state, event handlers, queries, useMountEffect, or key resets instead.
Overview
no-use-effect is an agent skill for the Build phase that enforces React patterns replacing useEffect with derived state, handlers, queries, useMountEffect, or key resets.
Install
npx skills add https://github.com/factory-ai/factory-plugins --skill no-use-effectWhat is this skill?
- Bans direct useEffect in favor of five documented replacement patterns
- Maps deriving state, fetching, events, mount sync, and prop-driven resets to concrete rules
- Pairs with no-restricted-syntax lint banning useEffect
- Includes useMountEffect escape hatch for one-time external sync on mount
- ACTIVATE on new components, refactors, PR review, or agent-added “just in case” effects
- Five replacement patterns plus useMountEffect escape hatch
Adoption & trust: 529 installs on skills.sh; 82 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your React code or agent drafts pile on useEffect for derivation, fetching, and events that React can handle more simply and safely elsewhere.
Who is it for?
Greenfield React UI, refactors from class-era thinking, and PR reviews where agents habitually add useEffect.
Skip if: Non-React frameworks, backend services, or rare cases already justified by useMountEffect after review.
When should I use this skill?
Writing React components, refactoring useEffect, reviewing PRs with useEffect, or when an agent adds useEffect just in case.
What do I get? / Deliverables
Components use the five replacement rules so effects are eliminated except via the documented useMountEffect escape hatch.
- Components refactored to Rule 1–5 patterns without direct useEffect
- PR review notes flagging unjustified effects and suggested replacements
Recommended Skills
Journey fit
How it compares
Use as a lint-backed skill companion to React docs—not a generic “React best practices” essay without enforceable replacements.
Common Questions / FAQ
Who is no-use-effect for?
Solo builders shipping React or React Native SaaS with Cursor, Claude Code, or Codex who want agents to stop defaulting to useEffect.
When should I use no-use-effect?
While building new components, refactoring existing useEffect calls, reviewing PRs that add effects, or when an agent adds useEffect just in case.
Is no-use-effect safe to install?
Review the Security Audits panel on this Prism page; the skill is documentation and lint guidance with no extra network calls by itself.
SKILL.md
READMESKILL.md - No Use Effect
# No useEffect Never call `useEffect` directly. Use derived state, event handlers, data-fetching libraries, or `useMountEffect` instead. ## Quick Reference - Lint rule: `no-restricted-syntax` (configured to ban `useEffect`) - React docs: [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect) - Origin: [https://x.com/alvinsng/status/2033969062834045089](https://x.com/alvinsng/status/2033969062834045089) | Instead of useEffect for... | Use | | --- | --- | | Deriving state from other state/props | Inline computation (Rule 1) | | Fetching data | `useQuery` / data-fetching library (Rule 2) | | Responding to user actions | Event handlers (Rule 3) | | One-time external sync on mount | `useMountEffect` (Rule 4) | | Resetting state when a prop changes | `key` prop on parent (Rule 5) | ## When to Use This Skill - Writing new React components - Refactoring existing `useEffect` calls - Reviewing PRs that introduce `useEffect` - An agent adds `useEffect` "just in case" ## Workflow ### 1. Identify the useEffect Determine what the effect is doing -- deriving state, fetching data, responding to an event, syncing with an external system, or resetting state. ### 2. Apply the Correct Replacement Pattern Use the five rules below to pick the right replacement. ### 3. Verify ``` npm run lint -- --filter=<package> npm run typecheck -- --filter=<package> npm run test -- --filter=<package> ``` ## The Escape Hatch: useMountEffect For the rare case where you need to sync with an external system on mount: The implementation wraps `useEffect` with an empty dependency array to make intent explicit: ``` export function useMountEffect(effect: () => void | (() => void)) { /* eslint-disable no-restricted-syntax */ useEffect(effect, []); } ``` ## Replacement Patterns ### Rule 1: Derive state, do not sync it Most effects that set state from other state are unnecessary and add extra renders. ``` // BAD: Two render cycles - first stale, then filtered function ProductList() { const [products, setProducts] = useState([]); const [filteredProducts, setFilteredProducts] = useState([]); useEffect(() => { setFilteredProducts(products.filter((p) => p.inStock)); }, [products]); } // GOOD: Compute inline in one render function ProductList() { const [products, setProducts] = useState([]); const filteredProducts = products.filter((p) => p.inStock); } ``` **Smell test:** You are about to write `useEffect(() => setX(deriveFromY(y)), [y])`, or you have state that only mirrors other state or props. ### Rule 2: Use data-fetching libraries Effect-based fetching creates race conditions and duplicated caching logic. ``` // BAD: Race condition risk function ProductPage({ productId }) { const [product, setProduct] = useState(null); useEffect(() => { fetchProduct(productId).then(setProduct); }, [productId]); } // GOOD: Query library handles cancellation/caching/staleness function ProductPage({ productId }) { const { data: product } = useQuery(['product', productId], () => fetchProduct(productId) ); } ``` **Smell test:** Your effect does `fetch(...)` and then `setState(...)`, or you are re-implementing caching, retries, cancellation, or stale handling. ### Rule 3: Event handlers, not effects If a user clicks a button, do the work in the handler. ``` // BAD: Effect as an action relay function LikeButton() { const [liked, setLiked] = useState(false); useEffect(() => { if (liked) { postLike(); setLiked(false); } }, [liked]); return <button onClick={() => setLiked(true)}>Like</button>; } // GOOD: Direct event-driven