## What does this PR do? This PR adds https://vercel.com/blog/introducing-react-best-practices for your coding agent using `npx add-skill vercel-labs/agent-skills` command The skills are added to `.claude/`, `.cursor/`, and `.opencode/` directories to provide React and Next.js performance optimization guidance for AI-assisted workflows. ## Updates since last revision Addressed Cubic AI review feedback for issues with confidence >= 9/10: - **rerender-dependencies.md** - Replaced `console.log(user.id)` with `fetchUserDetails(user.id)` to avoid logging sensitive information - **server-after-nonblocking.md** - Removed `sessionCookie` from `logUserAction` call to avoid logging sensitive authentication data, added `await` to async call - **bundle-conditional.md** - Added `loadError` state and `setLoadError` setter to fix undefined `setEnabled` reference - **advanced-event-handler-refs.md** - Updated `useWindowEvent` handler signature to accept `Event` parameter and forward it to the stored handler - **rerender-derived-state.md** - Closed `<nav>` elements in both examples for valid JSX Fixes applied to both `.claude` and `.cursor` skill directories for consistency. ## Mandatory Tasks (DO NOT REMOVE) - [x] I have self-reviewed the code (A decent size PR without self-review might be rejected). - [x] N/A I have updated the developer docs in /docs if this PR makes changes that would require a [documentation change](https://cal.com/docs). If N/A, write N/A here and check the checkbox. - [x] N/A, I confirm automated tests are in place that prove my fix is effective or that my feature works. ## How should this be tested? These are documentation files for AI coding agents. No runtime testing required - review the markdown files to verify the example code snippets are correct. ## Checklist for human review - [ ] Verify example code snippets in the skill files are syntactically correct - [ ] Confirm the fixes don't introduce new issues in the documentation examples - [ ] Check that `.claude` and `.cursor` directories have consistent content --- Link to Devin run: https://app.devin.ai/sessions/f7f7e67fdeea4b22a4817d63ed9e1759 Requested by: unknown ()
75 lines
2.9 KiB
Markdown
75 lines
2.9 KiB
Markdown
---
|
|
title: Use Functional setState Updates
|
|
impact: MEDIUM
|
|
impactDescription: prevents stale closures and unnecessary callback recreations
|
|
tags: react, hooks, useState, useCallback, callbacks, closures
|
|
---
|
|
|
|
## Use Functional setState Updates
|
|
|
|
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):**
|
|
|
|
```tsx
|
|
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):**
|
|
|
|
```tsx
|
|
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.
|