Use Functional setState Updates
Install this skill
npx skills add vercel-labs/agent-skillsWorks across Claude Code, Cursor, Codex, Copilot & Antigravity
Functional setState updates provide a pattern in React where the state setter receives a callback rather than a raw value. This method accepts the most current state as an argument, allowing you to compute new values without capturing them in a closure. By decoupling the update logic from the component's rendered state snapshot, you eliminate the need to include state variables in hook dependency arrays. This results in stable function identities, which minimizes unnecessary re-renders of memoized child components. Beyond performance, this approach is the primary defense against stale closure bugs, ensuring that rapid or asynchronous state updates always process the most accurate, up-to-date data. It shifts the responsibility of tracking state versioning to the React runtime, creating cleaner and more predictable component lifecycle management.
When to Use This Skill
- •Incrementing counters based on previous values
- •Removing items from an array inside a memoized handler
- •Managing complex object state updates inside hooks
- •Performing sequential updates within async blocks
How to Invoke This Skill
Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:
- “refactor this code to remove stale closures
- “fix unnecessary re-renders in my component callbacks
- “how to update state without including it in dependency array
- “optimize my useCallback hooks for performance
- “ensure my state updates use the latest values
What this skill does
- •Eliminates stale closure bugs by accessing current state snapshots
- •Removes unnecessary state dependencies from useCallback hooks
- •Stabilizes callback references to prevent downstream re-renders
- •Enables safe concurrent state updates in asynchronous operations
- •Simplifies dependency management inside event handlers
When not to use it
- ✕When setting state to a static, known value
- ✕When updating state strictly based on new props or user input
- ✕When the next state value is entirely independent of the current state
Example workflow
- Identify a component where state updates trigger frequent re-renders of children
- Pinpoint useCallback hooks that list state variables in their dependency arrays
- Replace direct state setters with the functional callback syntax
- Remove the now-unnecessary state dependencies from the dependency array
- Verify that the callback identity remains stable across re-renders
Prerequisites
- –Basic knowledge of React useState hook
- –Understanding of JavaScript closures
- –Familiarity with useCallback dependencies
Pitfalls & limitations
- !Overusing functional updates when a simple static value setter suffices
- !Assuming functional updates are necessary for all setter calls, which adds unnecessary boilerplate
- !Misunderstanding that while functional updates fix stale closures, they do not change the execution order of React updates
FAQ
How it compares
Generic prompts often suggest using refs to bypass state issues, whereas functional updates solve the same problem while keeping state strictly within the React data flow.
📄 Full skill instructions — original source: vercel-labs/agent-skills
When updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.
**Incorrect (requires state as dependency):**
function TodoList() {
const [items, setItems] = useState(initialItems)
// Callback must depend on items, recreated on every items change
const addItems = useCallback((newItems: Item[]) => {
setItems([...items, ...newItems])
}, [items]) // ❌ items dependency causes recreations
// Risk of stale closure if dependency is forgotten
const removeItem = useCallback((id: string) => {
setItems(items.filter(item => item.id !== id))
}, []) // ❌ Missing items dependency - will use stale items!
return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
}The first callback is recreated every time
items changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial items value.**Correct (stable callbacks, no stale closures):**
function TodoList() {
const [items, setItems] = useState(initialItems)
// Stable callback, never recreated
const addItems = useCallback((newItems: Item[]) => {
setItems(curr => [...curr, ...newItems])
}, []) // ✅ No dependencies needed
// Always uses latest state, no stale closure risk
const removeItem = useCallback((id: string) => {
setItems(curr => curr.filter(item => item.id !== id))
}, []) // ✅ Safe and stable
return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
}**Benefits:**
1. **Stable callback references** - Callbacks don't need to be recreated when state changes
2. **No stale closures** - Always operates on the latest state value
3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks
4. **Prevents bugs** - Eliminates the most common source of React closure bugs
**When to use functional updates:**
- Any setState that depends on the current state value
- Inside useCallback/useMemo when state is needed
- Event handlers that reference state
- Async operations that update state
**When direct updates are fine:**
- Setting state to a static value:
setCount(0)- Setting state from props/arguments only:
setName(newName)- State doesn't depend on previous value
**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.
How to Use This Skill Unit
Option A: Project-Specific (Recommended)
- Click "Download" above
- In your project, create the directory:
.agent/skills/rerender-functional-setstate/ - Save the file as
SKILL.md - The agent will automatically discover the skill based on its description.
Option B: Global Installation (All Agents)
Save the file to these locations to make it available across all projects:
- Claude Code:
~/.claude/skills/vercel-labs/agent-skills/rerender-functional-setstate/SKILL.md - Cursor:
~/.cursor/skills/vercel-labs/agent-skills/rerender-functional-setstate/SKILL.md - Antigravity:
~/.gemini/antigravity/skills/vercel-labs/agent-skills/rerender-functional-setstate/SKILL.md
🚀 Install with CLI:npx skills add vercel-labs/agent-skills