Skip to content

Instantly share code, notes, and snippets.

@zmajstor
Last active April 5, 2026 10:11
Show Gist options
  • Select an option

  • Save zmajstor/21a52c03df98826c7d2c10352ad1d25d to your computer and use it in GitHub Desktop.

Select an option

Save zmajstor/21a52c03df98826c7d2c10352ad1d25d to your computer and use it in GitHub Desktop.
name analyze-effects
description Identify all useEffect hooks in a folder and classify them, then apply the correct replacement pattern from "You Might Not Need an Effect"
argument-hint [folder-path]

Analyze useEffect Usage

Analyze all useEffect (and useLayoutEffect) hooks in the specified folder and recommend the safest replacement patterns.

Target folder: $ARGUMENTS

If no folder is provided, ask the user which folder to analyze.

Do not edit code as part of this skill unless the user explicitly asks for a refactor after reviewing the analysis.

Step 1: Identify and Classify

Find every useEffect and useLayoutEffect in the target folder (excluding test files). Read each file fully before classifying any effect. For each one, classify it into one of these categories:

Category Description
Deriving state Sets state based on other state/props — usually unnecessary
Fetching data Calls an API and sets state with the result
Responding to event Does work that should happen in an event handler instead
Syncing with external system DOM manipulation, subscriptions, browser APIs, third-party widgets
Resetting state on prop change Resets local state when a prop/dependency changes

For each effect, report:

  • File and line number
  • Category from the table above
  • What it does (one sentence)
  • Verdict: KEEP, REFACTOR, or INVESTIGATE
    • KEEP — legitimate external sync or mount-only setup
    • REFACTOR — can be replaced with a better pattern
    • INVESTIGATE — needs deeper analysis or discussion

Step 2: Recommend Replacement Patterns

For each effect marked REFACTOR, recommend the best replacement using these five rules:

  • Prefer patterns that already exist in the codebase.
  • Do not invent custom hooks, helper APIs, or new libraries unless they are already present in the repo or the user asked for that kind of change.
  • If more than one replacement is plausible, explain the tradeoff briefly and prefer INVESTIGATE over overconfident advice.

Rule 1: Derive state, do not sync it

Most effects that set state from another state are unnecessary and add extra renders.

// BAD: Two render cycles
useEffect(() => {
  setFilteredProducts(products.filter((p) => p.inStock));
}, [products]);

// GOOD: Compute inline
const filteredProducts = products.filter((p) => p.inStock);

Smell test: useEffect(() => setX(deriveFromY(y)), [y]), or state that only mirrors other state or props.

Rule 2: Prefer the app's existing data-loading pattern

Effect-based fetching creates race conditions and duplicated caching logic.

// BAD: Race condition risk
useEffect(() => {
  fetchProduct(productId).then(setProduct);
}, [productId]);

// GOOD: Reuse the project's existing loader/query pattern
const { data: product } = useQuery(['product', productId], () =>
  fetchProduct(productId)
);

Smell test: Effect does fetch(...) then setState(...), or re-implements caching/retries/cancellation.

Prefer framework loaders, Server Components, or an existing query library when the repo already uses them. If the project has no established pattern, recommend INVESTIGATE instead of prescribing a new dependency.

Rule 3: Event handlers, not effects

If a user action triggers the work, do it in the handler.

// BAD: Effect as an action relay
useEffect(() => {
  if (liked) { postLike(); setLiked(false); }
}, [liked]);

// GOOD: Direct event-driven action
<button onClick={() => postLike()}>Like</button>

Smell test: State used as a flag so an effect can do the real action.

Rule 4: Keep effects for true external synchronization

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

// GOOD: Mount-only external setup with cleanup
useEffect(() => {
  connectionManager.on('connected', handleConnect);
  return () => connectionManager.off('connected', handleConnect);
}, []);

Smell test: Synchronizing with an external system, behavior is "setup on mount, cleanup on unmount."

Only recommend project-specific helpers such as useMountEffect if they already exist in the codebase.

Rule 5: Reset with key, not dependency choreography

// BAD: Effect resets local state on prop change
useEffect(() => { setComment(''); }, [videoId]);

// GOOD: key forces clean remount
<CommentBox key={videoId} videoId={videoId} />

Smell test: Effect's only job is resetting local state when an ID/prop changes.

Output Format

Present results as a table first (summary), then detailed analysis for each REFACTOR item with the recommended code change. Group by file.

Do NOT propose changes to code you haven't read. Read each file fully before analyzing. Do NOT claim a refactor is safe unless the replacement fits the repo's current patterns and constraints.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment