[ PROMPT_NODE_25293 ]
Anti Patterns
[ SKILL_DOCUMENTATION ]
# useEffect Anti-Patterns
## 1. Redundant State for Derived Values
```tsx
// BAD: Extra state + Effect for derived value
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
}
// GOOD: Calculate during rendering
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const fullName = firstName + ' ' + lastName; // Just compute it
}
```
**Why it's bad**: Causes extra render pass with stale value, then re-renders with updated value.
---
## 2. Filtering/Transforming Data in Effect
```tsx
// BAD: Effect to filter list
function TodoList({ todos, filter }) {
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
setVisibleTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);
}
// GOOD: Filter during render (memoize if expensive)
function TodoList({ todos, filter }) {
const visibleTodos = useMemo(
() => getFilteredTodos(todos, filter),
[todos, filter]
);
}
```
---
## 3. Resetting State on Prop Change
```tsx
// BAD: Effect to reset state
function ProfilePage({ userId }) {
const [comment, setComment] = useState('');
useEffect(() => {
setComment('');
}, [userId]);
}
// GOOD: Use key prop
function ProfilePage({ userId }) {
return ;
}
function Profile({ userId }) {
const [comment, setComment] = useState(''); // Resets automatically
}
```
**Why key works**: React treats components with different keys as different components, recreating state.
---
## 4. Event-Specific Logic in Effect
```tsx
// BAD: Effect for button click result
function ProductPage({ product, addToCart }) {
useEffect(() => {
if (product.isInCart) {
showNotification(`Added ${product.name}!`);
}
}, [product]);
function handleBuyClick() {
addToCart(product);
}
}
// GOOD: Handle in event handler
function ProductPage({ product, addToCart }) {
function handleBuyClick() {
addToCart(product);
showNotification(`Added ${product.name}!`);
}
}
```
**Why it's bad**: Effect fires on page refresh (isInCart is true), showing notification unexpectedly.
---
## 5. Chains of Effects
```tsx
// BAD: Effects triggering each other
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
const [isGameOver, setIsGameOver] = useState(false);
useEffect(() => {
if (card?.gold) setGoldCardCount(c => c + 1);
}, [card]);
useEffect(() => {
if (goldCardCount > 3) {
setRound(r => r + 1);
setGoldCardCount(0);
}
}, [goldCardCount]);
useEffect(() => {
if (round > 5) setIsGameOver(true);
}, [round]);
}
// GOOD: Calculate in event handler
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
const isGameOver = round > 5; // Derived!
function handlePlaceCard(nextCard) {
if (isGameOver) throw Error('Game ended');
setCard(nextCard);
if (nextCard.gold) {
if (goldCardCount setGoldCardCount -> setRound -> setIsGameOver). Also fragile for features like history replay.
---
## 6. Notifying Parent via Effect
```tsx
// BAD: Effect to notify parent
function Toggle({ onChange }) {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
onChange(isOn);
}, [isOn, onChange]);
function handleClick() {
setIsOn(!isOn);
}
}
// GOOD: Notify in same event
function Toggle({ onChange }) {
const [isOn, setIsOn] = useState(false);
function updateToggle(nextIsOn) {
setIsOn(nextIsOn);
onChange(nextIsOn); // Same event, batched render
}
function handleClick() {
updateToggle(!isOn);
}
}
// BEST: Fully controlled component
function Toggle({ isOn, onChange }) {
function handleClick() {
onChange(!isOn);
}
}
```
---
## 7. Passing Data Up to Parent
```tsx
// BAD: Child fetches, passes up via Effect
function Parent() {
const [data, setData] = useState(null);
return ;
}
function Child({ onFetched }) {
const data = useSomeAPI();
useEffect(() => {
if (data) onFetched(data);
}, [onFetched, data]);
}
// GOOD: Parent fetches, passes down
function Parent() {
const data = useSomeAPI();
return ;
}
```
**Why**: Data should flow down. Upward flow via Effects makes debugging hard.
---
## 8. Fetching Without Cleanup (Race Condition)
```tsx
// BAD: No cleanup - race condition
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
fetchResults(query).then(json => {
setResults(json); // "hello" response may arrive after "hell"
});
}, [query]);
}
// GOOD: Cleanup ignores stale responses
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
let ignore = false;
fetchResults(query).then(json => {
if (!ignore) setResults(json);
});
return () => { ignore = true; };
}, [query]);
}
```
---
## 9. App Initialization in Effect
```tsx
// BAD: Runs twice in dev, may break auth
function App() {
useEffect(() => {
loadDataFromLocalStorage();
checkAuthToken(); // May invalidate token on second call!
}, []);
}
// GOOD: Module-level guard
let didInit = false;
function App() {
useEffect(() => {
if (!didInit) {
didInit = true;
loadDataFromLocalStorage();
checkAuthToken();
}
}, []);
}
// ALSO GOOD: Module-level execution
if (typeof window !== 'undefined') {
checkAuthToken();
loadDataFromLocalStorage();
}
```
Source: claude-code-templates (MIT). See About Us for full credits.