react-effect-patterns

Guidelines for proper React useEffect usage and avoiding unnecessary Effects. Use when writing, reviewing, or refactoring React components that use useEffect, useState, or handle side effects. Triggers on tasks involving React Effects, derived state, event handlers, data fetching, or component synchronization.

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-effect-patterns" with this command: npx skills add nbbaier/agent-skills/nbbaier-agent-skills-react-effect-patterns

React Effect Patterns

Effects are an escape hatch from React for synchronizing with external systems. Removing unnecessary Effects makes code easier to follow, faster to run, and less error-prone.

When to Use Effects

  • Synchronizing with external systems (non-React widgets, network, browser DOM)
  • Analytics on component display
  • Data fetching (with cleanup for race conditions)

When NOT to Use Effects

  • Transforming data for rendering
  • Handling user events
  • Updating state based on other state

Decision Tree

Why does this code run?

  • Component displayed → Effect
  • User action → Event handler
  • Calculable from props/state → Calculate during render

Anti-Patterns and Solutions

1. Derived State

// ❌ Bad
const [fullName, setFullName] = useState("");
useEffect(() => {
   setFullName(firstName + " " + lastName);
}, [firstName, lastName]);

// ✅ Good - calculate during render
const fullName = firstName + " " + lastName;

2. Expensive Calculations

// ❌ Bad
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
   setVisibleTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);

// ✅ Good - useMemo for expensive operations
const visibleTodos = useMemo(
   () => getFilteredTodos(todos, filter),
   [todos, filter],
);

Use console.time()/console.timeEnd() to measure. Memoize if ≥1ms.

3. Reset State on Prop Change

// ❌ Bad
useEffect(() => {
   setComment("");
}, [userId]);

// ✅ Good - use key to reset
<Profile userId={userId} key={userId} />;

4. Adjust Part of State on Prop Change

// ❌ Bad
useEffect(() => {
   setSelection(null);
}, [items]);

// ✅ Good - derive from state
const selection = items.find((item) => item.id === selectedId) ?? null;

5. Event-Specific Logic

// ❌ Bad
useEffect(() => {
   if (product.isInCart) {
      showNotification("Added " + product.name + "!");
   }
}, [product]);

// ✅ Good - in event handler
function handleBuyClick() {
   addToCart(product);
   showNotification("Added " + product.name + "!");
}

6. Form Submission

// ❌ Bad
useEffect(() => {
   if (jsonToSubmit !== null) {
      post("/api/register", jsonToSubmit);
   }
}, [jsonToSubmit]);

// ✅ Good - in event handler
function handleSubmit(e) {
   e.preventDefault();
   post("/api/register", { firstName, lastName });
}

7. Effect Chains

// ❌ Bad - cascading Effects
useEffect(() => {
   setGoldCardCount((c) => c + 1);
}, [card]);
useEffect(() => {
   setRound((r) => r + 1);
}, [goldCardCount]);
useEffect(() => {
   setIsGameOver(true);
}, [round]);

// ✅ Good - calculate + update in handler
const isGameOver = round > 5;

function handlePlaceCard(nextCard) {
   setCard(nextCard);
   if (nextCard.gold) {
      if (goldCardCount < 3) {
         setGoldCardCount(goldCardCount + 1);
      } else {
         setGoldCardCount(0);
         setRound(round + 1);
      }
   }
}

8. App Initialization

// ❌ Bad - runs twice in dev
useEffect(() => {
   loadDataFromLocalStorage();
   checkAuthToken();
}, []);

// ✅ Good - module level or guard
let didInit = false;
function App() {
   useEffect(() => {
      if (!didInit) {
         didInit = true;
         loadDataFromLocalStorage();
         checkAuthToken();
      }
   }, []);
}

9. Notify Parent of State Changes

// ❌ Bad - extra render pass
useEffect(() => {
   onChange(isOn);
}, [isOn, onChange]);

// ✅ Good - update both in handler
function updateToggle(nextIsOn) {
   setIsOn(nextIsOn);
   onChange(nextIsOn);
}

// ✅ Also good - lift state (controlled component)
function Toggle({ isOn, onChange }) {
   function handleClick() {
      onChange(!isOn);
   }
}

10. Pass Data to Parent

// ❌ Bad - child fetches, passes up
useEffect(() => {
   if (data) onFetched(data);
}, [data]);

// ✅ Good - parent fetches, passes down
function Parent() {
   const data = useSomeAPI();
   return <Child data={data} />;
}

11. External Store Subscription

// ❌ Bad - manual subscription
useEffect(() => {
   const handler = () => setIsOnline(navigator.onLine);
   window.addEventListener("online", handler);
   window.addEventListener("offline", handler);
   return () => {
      window.removeEventListener("online", handler);
      window.removeEventListener("offline", handler);
   };
}, []);

// ✅ Good - useSyncExternalStore
return useSyncExternalStore(
   subscribe,
   () => navigator.onLine,
   () => true,
);

12. Data Fetching with Race Conditions

// ✅ Correct - cleanup ignores stale responses
useEffect(() => {
   let ignore = false;
   fetchResults(query).then((json) => {
      if (!ignore) setResults(json);
   });
   return () => {
      ignore = true;
   };
}, [query]);

Quick Reference

ScenarioSolution
Transform dataCalculate during render
Expensive calculationuseMemo
Reset all state on propkey attribute
Adjust state on propDerive during render
Share event logicExtract function, call from handlers
User eventsEvent handlers
External system syncEffect
Notify parentUpdate in handler or lift state
Init onceModule-level or guard variable
External storeuseSyncExternalStore
Fetch dataEffect with cleanup

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.

Automation

ideation

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

implementation-guide

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

logging-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review