From 4492fa167d50c7d430e78a73fbff7455a7855e85 Mon Sep 17 00:00:00 2001 From: whincwu Date: Sun, 22 Mar 2026 16:38:15 +0800 Subject: [PATCH] docs(useeffect): add 'adjust state during render' pattern Add missing case from React docs "Adjusting some state when a prop changes": - Add Quick Reference entry for partial state reset scenario - Add new section in alternatives.md with prevItems pattern - Update Decision Tree to distinguish reset ALL vs SOME state - Update Summary table with new pattern This fills the gap between "key prop" (reset all) and "store ID" (derive) approaches. --- skills/react-useeffect/SKILL.md | 9 +++- skills/react-useeffect/alternatives.md | 60 +++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/skills/react-useeffect/SKILL.md b/skills/react-useeffect/SKILL.md index d7c6ffb..d6614e3 100644 --- a/skills/react-useeffect/SKILL.md +++ b/skills/react-useeffect/SKILL.md @@ -14,6 +14,7 @@ Effects are an **escape hatch** from React. They let you synchronize with extern | Derived state from props/state | `useState` + `useEffect` | Calculate during render | | Expensive calculations | `useEffect` to cache | `useMemo` | | Reset state on prop change | `useEffect` with `setState` | `key` prop | +| Adjust some state on prop change | `useEffect` with `setState` | Adjust during render (`prevItems` pattern) | | User event responses | `useEffect` watching state | Event handler directly | | Notify parent of changes | `useEffect` calling `onChange` | Call in event handler | | Fetch data | `useEffect` without cleanup | `useEffect` with cleanup OR framework | @@ -43,8 +44,12 @@ Need to respond to something? ├── Props/state changed and need derived value? │ └── CALCULATE DURING RENDER │ └── Expensive? Use useMemo -└── Need to reset state when prop changes? - └── Use KEY PROP on component +└── Prop changed and need to adjust state? + ├── Reset ALL state? + │ └── Use KEY PROP on component + └── Adjust SOME state? + └── ADJUST DURING RENDER (prevItems pattern) + └── Or store ID and derive object ``` ## Detailed Guidance diff --git a/skills/react-useeffect/alternatives.md b/skills/react-useeffect/alternatives.md index 791744a..5d7088e 100644 --- a/skills/react-useeffect/alternatives.md +++ b/skills/react-useeffect/alternatives.md @@ -72,7 +72,56 @@ function Profile({ userId }) { --- -## 4. Store ID Instead of Object +## 4. Adjust State During Render (Partial Reset) + +When you need to adjust *some* (not all) state when a prop changes: + +```tsx +// BAD: Effect causes extra render pass +function List({ items }) { + const [selection, setSelection] = useState(null); + + useEffect(() => { + setSelection(null); // Extra render after DOM update + }, [items]); +} + +// BETTER: Adjust state during render +function List({ items }) { + const [selection, setSelection] = useState(null); + const [prevItems, setPrevItems] = useState(items); + + if (items !== prevItems) { + setPrevItems(items); // Remember for next render + setSelection(null); // Adjust before React renders children + } +} + +// BEST: Calculate during render (if possible) +function List({ items }) { + const [selectedId, setSelectedId] = useState(null); + const selection = items.find(item => item.id === selectedId) ?? null; +} +``` + +**How render-time adjustment works:** +- React detects `setState` calls during render +- React discards the returned JSX and immediately re-renders +- No DOM update or child render with stale value + +**Important constraints:** +- Can only update *same component's* state (not other components') +- Must have a condition like `items !== prevItems` to avoid infinite loop +- Most components don't need this — prefer `key` or derived state first + +**Priority:** +1. `key` prop (resets ALL state) +2. Calculate during render (derive what you need) +3. Adjust state during render (partial reset only when necessary) + +--- + +## 5. Store ID Instead of Object To preserve selection when list changes: @@ -99,7 +148,7 @@ function List({ items }) { --- -## 5. Event Handlers for User Actions +## 6. Event Handlers for User Actions User clicks/submits/drags should be handled in event handlers, not Effects: @@ -134,7 +183,7 @@ function handleCheckoutClick() { buyProduct(); navigateTo('/checkout'); } --- -## 6. useSyncExternalStore for External Stores +## 7. useSyncExternalStore for External Stores For subscribing to external data (browser APIs, third-party stores): @@ -179,7 +228,7 @@ function useOnlineStatus() { --- -## 7. Lifting State Up +## 8. Lifting State Up When two components need synchronized state, lift it to common ancestor: @@ -199,7 +248,7 @@ function Parent() { --- -## 8. Custom Hooks for Data Fetching +## 9. Custom Hooks for Data Fetching Extract fetch logic with proper cleanup: @@ -251,6 +300,7 @@ function SearchResults({ query }) { | Value from props/state | Calculate during render | | Expensive calculation | `useMemo` | | Reset all state on prop change | `key` prop | +| Adjust some state on prop change | Adjust state during render (`prevItems` pattern) | | Respond to user action | Event handler | | Sync with external system | `useEffect` with cleanup | | Subscribe to external store | `useSyncExternalStore` |