No Use Effect

Factory-AI/factory-plugins/plugins/typescript/skills/no-use-effect

by Factory-AI7166a0799e3dd39184068160b9caaa1e6469028aNo licenseListed Oct 9, 2026Updated Oct 9, 2026

Enforce the no-useEffect rule when writing or reviewing React code. ACTIVATE when writing React components, refactoring existing useEffect calls, reviewing PRs with useEffect, or when an agent adds useEffect "just in case." Provides the five replacement patterns and the useMountEffect escape hatch.

Instructions onlySoftware Development
AI-generated overview

Enforces a no-useEffect rule in React code by prescribing five replacement patterns and a useMountEffect escape hatch.

What it does
This skill guides an agent to avoid calling useEffect directly when writing or reviewing React components. It classifies what an effect is doing and maps it to one of five replacement patterns: inline derived state, data-fetching libraries, event handlers, useMountEffect for one-time external sync, and key-based remounting. It also describes a component structure convention and verification commands for lint, typecheck, and tests.
When to use it
Use it when writing new React components, refactoring existing useEffect calls, reviewing pull requests that introduce useEffect, or when an agent adds useEffect defensively. It is intended for React codebases that want to ban direct useEffect usage.
Requirements
Instructions only; no scripts are shipped. It references a lint rule configuration, React data-fetching libraries such as useQuery, and project commands for lint, typecheck, and tests.

No useEffect

Never call useEffect directly. Use derived state, event handlers, data-fetching libraries, or useMountEffect instead.

Quick Reference

Instead of useEffect for...Use
Deriving state from other state/propsInline computation (Rule 1)
Fetching datauseQuery / data-fetching library (Rule 2)
Responding to user actionsEvent handlers (Rule 3)
One-time external sync on mountuseMountEffect (Rule 4)
Resetting state when a prop changeskey 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 filteredfunction ProductList() {  const [products, setProducts] = useState([]);  const [filteredProducts, setFilteredProducts] = useState([]);
  useEffect(() => {    setFilteredProducts(products.filter((p) => p.inStock));  }, [products]);}
// GOOD: Compute inline in one renderfunction 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 riskfunction ProductPage({ productId }) {  const [product, setProduct] = useState(null);
  useEffect(() => {    fetchProduct(productId).then(setProduct);  }, [productId]);}
// GOOD: Query library handles cancellation/caching/stalenessfunction 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 relayfunction LikeButton() {  const [liked, setLiked] = useState(false);
  useEffect(() => {    if (liked) {      postLike();      setLiked(false);    }  }, [liked]);
  return <button onClick={() => setLiked(true)}>Like</button>;}
// GOOD: Direct event-driven actionfunction LikeButton() {  return <button onClick={() => postLike()}>Like</button>;}

Smell test: State is used as a flag so an effect can do the real action, or you are building "set flag -> effect runs -> reset flag" mechanics.

Rule 4: useMountEffect for one-time external sync

Good uses: DOM integration (focus, scroll), third-party widget lifecycles, browser API subscriptions.

// BAD: Guard inside effectfunction VideoPlayer({ isLoading }) {  useEffect(() => {    if (!isLoading) playVideo();  }, [isLoading]);}
// GOOD: Mount only when preconditions are metfunction VideoPlayerWrapper({ isLoading }) {  if (isLoading) return <LoadingScreen />;  return <VideoPlayer />;}
function VideoPlayer() {  useMountEffect(() => playVideo());}

Use useMountEffect for stable dependencies (singletons, refs, context values that never change):

// BAD: useEffect with dependency that never changesuseEffect(() => {  connectionManager.on('connected', handleConnect);  return () => connectionManager.off('connected', handleConnect);}, [connectionManager]); // connectionManager is a singleton from context
// GOOD: useMountEffect for stable dependencies
useMountEffect(() => {  connectionManager.on('connected', handleConnect);  return () => connectionManager.off('connected', handleConnect);});

Smell test: You are synchronizing with an external system, and the behavior is naturally "setup on mount, cleanup on unmount."

Rule 5: Reset with key, not dependency choreography

// BAD: Effect attempts to emulate remount behaviorfunction VideoPlayer({ videoId }) {  useEffect(() => {    loadVideo(videoId);  }, [videoId]);}
// GOOD: key forces clean remountfunction VideoPlayer({ videoId }) {  useMountEffect(() => {    loadVideo(videoId);  });}
function VideoPlayerWrapper({ videoId }) {  return <VideoPlayer key={videoId} videoId={videoId} />;}

Smell test: You are writing an effect whose only job is to reset local state when an ID/prop changes, or you want the component to behave like a brand-new instance for each entity.

Component Structure Convention

Computed values come after hooks and local state, never via useEffect:

export function FeatureComponent({ featureId }: ComponentProps) {  // Hooks first  const { data, isLoading } = useQueryFeature(featureId);
  // Local state  const [isOpen, setIsOpen] = useState(false);
  // Computed values (NOT useEffect + setState)  const displayName = user?.name ?? 'Unknown';
  // Event handlers  const handleClick = () => { setIsOpen(true); };
  // Early returns  if (isLoading) return <Loading />;
  // Render  return <Flex direction="column" gap="lg">...</Flex>;}

Source and attribution

Source:Factory-AI/factory-pluginsinplugins/typescript/skills/no-use-effectat commit7166a07

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal