Skip to content

Commit 2cbbb05

Browse files
committed
Merge branch 'master' into agent-practices-c32c
# Conflicts: # components/notification-generator/notification-generator.tsx
2 parents a43efa9 + 3af8436 commit 2cbbb05

File tree

85 files changed

+3149
-458
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+3149
-458
lines changed

.agent/skills/vercel-react-best-practices/AGENTS.md

Lines changed: 243 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,28 @@ Comprehensive performance optimization guide for React and Next.js applications,
4646
- 4.3 [Use SWR for Automatic Deduplication](#43-use-swr-for-automatic-deduplication)
4747
- 4.4 [Version and Minimize localStorage Data](#44-version-and-minimize-localstorage-data)
4848
5. [Re-render Optimization](#5-re-render-optimization)**MEDIUM**
49-
- 5.1 [Defer State Reads to Usage Point](#51-defer-state-reads-to-usage-point)
50-
- 5.2 [Do not wrap a simple expression with a primitive result type in useMemo](#52-do-not-wrap-a-simple-expression-with-a-primitive-result-type-in-usememo)
51-
- 5.3 [Extract Default Non-primitive Parameter Value from Memoized Component to Constant](#53-extract-default-non-primitive-parameter-value-from-memoized-component-to-constant)
52-
- 5.4 [Extract to Memoized Components](#54-extract-to-memoized-components)
53-
- 5.5 [Narrow Effect Dependencies](#55-narrow-effect-dependencies)
54-
- 5.6 [Subscribe to Derived State](#56-subscribe-to-derived-state)
55-
- 5.7 [Use Functional setState Updates](#57-use-functional-setstate-updates)
56-
- 5.8 [Use Lazy State Initialization](#58-use-lazy-state-initialization)
57-
- 5.9 [Use Transitions for Non-Urgent Updates](#59-use-transitions-for-non-urgent-updates)
49+
- 5.1 [Calculate Derived State During Rendering](#51-calculate-derived-state-during-rendering)
50+
- 5.2 [Defer State Reads to Usage Point](#52-defer-state-reads-to-usage-point)
51+
- 5.3 [Do not wrap a simple expression with a primitive result type in useMemo](#53-do-not-wrap-a-simple-expression-with-a-primitive-result-type-in-usememo)
52+
- 5.4 [Extract Default Non-primitive Parameter Value from Memoized Component to Constant](#54-extract-default-non-primitive-parameter-value-from-memoized-component-to-constant)
53+
- 5.5 [Extract to Memoized Components](#55-extract-to-memoized-components)
54+
- 5.6 [Narrow Effect Dependencies](#56-narrow-effect-dependencies)
55+
- 5.7 [Put Interaction Logic in Event Handlers](#57-put-interaction-logic-in-event-handlers)
56+
- 5.8 [Subscribe to Derived State](#58-subscribe-to-derived-state)
57+
- 5.9 [Use Functional setState Updates](#59-use-functional-setstate-updates)
58+
- 5.10 [Use Lazy State Initialization](#510-use-lazy-state-initialization)
59+
- 5.11 [Use Transitions for Non-Urgent Updates](#511-use-transitions-for-non-urgent-updates)
60+
- 5.12 [Use useRef for Transient Values](#512-use-useref-for-transient-values)
5861
6. [Rendering Performance](#6-rendering-performance)**MEDIUM**
5962
- 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element)
6063
- 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists)
6164
- 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements)
6265
- 6.4 [Optimize SVG Precision](#64-optimize-svg-precision)
6366
- 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering)
64-
- 6.6 [Use Activity Component for Show/Hide](#66-use-activity-component-for-showhide)
65-
- 6.7 [Use Explicit Conditional Rendering](#67-use-explicit-conditional-rendering)
66-
- 6.8 [Use useTransition Over Manual Loading States](#68-use-usetransition-over-manual-loading-states)
67+
- 6.6 [Suppress Expected Hydration Mismatches](#66-suppress-expected-hydration-mismatches)
68+
- 6.7 [Use Activity Component for Show/Hide](#67-use-activity-component-for-showhide)
69+
- 6.8 [Use Explicit Conditional Rendering](#68-use-explicit-conditional-rendering)
70+
- 6.9 [Use useTransition Over Manual Loading States](#69-use-usetransition-over-manual-loading-states)
6771
7. [JavaScript Performance](#7-javascript-performance)**LOW-MEDIUM**
6872
- 7.1 [Avoid Layout Thrashing](#71-avoid-layout-thrashing)
6973
- 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups)
@@ -78,8 +82,9 @@ Comprehensive performance optimization guide for React and Next.js applications,
7882
- 7.11 [Use Set/Map for O(1) Lookups](#711-use-setmap-for-o1-lookups)
7983
- 7.12 [Use toSorted() Instead of sort() for Immutability](#712-use-tosorted-instead-of-sort-for-immutability)
8084
8. [Advanced Patterns](#8-advanced-patterns)**LOW**
81-
- 8.1 [Store Event Handlers in Refs](#81-store-event-handlers-in-refs)
82-
- 8.2 [useEffectEvent for Stable Callback Refs](#82-useeffectevent-for-stable-callback-refs)
85+
- 8.1 [Initialize App Once, Not Per Mount](#81-initialize-app-once-not-per-mount)
86+
- 8.2 [Store Event Handlers in Refs](#82-store-event-handlers-in-refs)
87+
- 8.3 [useEffectEvent for Stable Callback Refs](#83-useeffectevent-for-stable-callback-refs)
8388

8489
---
8590

@@ -1278,7 +1283,43 @@ function cachePrefs(user: FullUser) {
12781283

12791284
Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness.
12801285

1281-
### 5.1 Defer State Reads to Usage Point
1286+
### 5.1 Calculate Derived State During Rendering
1287+
1288+
**Impact: MEDIUM (avoids redundant renders and state drift)**
1289+
1290+
If a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift. Do not set state in effects solely in response to prop changes; prefer derived values or keyed resets instead.
1291+
1292+
**Incorrect: redundant state and effect**
1293+
1294+
```tsx
1295+
function Form() {
1296+
const [firstName, setFirstName] = useState('First')
1297+
const [lastName, setLastName] = useState('Last')
1298+
const [fullName, setFullName] = useState('')
1299+
1300+
useEffect(() => {
1301+
setFullName(firstName + ' ' + lastName)
1302+
}, [firstName, lastName])
1303+
1304+
return <p>{fullName}</p>
1305+
}
1306+
```
1307+
1308+
**Correct: derive during render**
1309+
1310+
```tsx
1311+
function Form() {
1312+
const [firstName, setFirstName] = useState('First')
1313+
const [lastName, setLastName] = useState('Last')
1314+
const fullName = firstName + ' ' + lastName
1315+
1316+
return <p>{fullName}</p>
1317+
}
1318+
```
1319+
1320+
Reference: [https://react.dev/learn/you-might-not-need-an-effect](https://react.dev/learn/you-might-not-need-an-effect)
1321+
1322+
### 5.2 Defer State Reads to Usage Point
12821323

12831324
**Impact: MEDIUM (avoids unnecessary subscriptions)**
12841325

@@ -1313,7 +1354,7 @@ function ShareButton({ chatId }: { chatId: string }) {
13131354
}
13141355
```
13151356

1316-
### 5.2 Do not wrap a simple expression with a primitive result type in useMemo
1357+
### 5.3 Do not wrap a simple expression with a primitive result type in useMemo
13171358

13181359
**Impact: LOW-MEDIUM (wasted computation on every render)**
13191360

@@ -1345,7 +1386,7 @@ function Header({ user, notifications }: Props) {
13451386
}
13461387
```
13471388

1348-
### 5.3 Extract Default Non-primitive Parameter Value from Memoized Component to Constant
1389+
### 5.4 Extract Default Non-primitive Parameter Value from Memoized Component to Constant
13491390

13501391
**Impact: MEDIUM (restores memoization by using a constant for default value)**
13511392

@@ -1377,7 +1418,7 @@ const UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () =
13771418
<UserAvatar />
13781419
```
13791420
1380-
### 5.4 Extract to Memoized Components
1421+
### 5.5 Extract to Memoized Components
13811422
13821423
**Impact: MEDIUM (enables early returns)**
13831424
@@ -1417,7 +1458,7 @@ function Profile({ user, loading }: Props) {
14171458
14181459
**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders.
14191460
1420-
### 5.5 Narrow Effect Dependencies
1461+
### 5.6 Narrow Effect Dependencies
14211462
14221463
**Impact: LOW (minimizes effect re-runs)**
14231464
@@ -1458,7 +1499,48 @@ useEffect(() => {
14581499
}, [isMobile])
14591500
```
14601501
1461-
### 5.6 Subscribe to Derived State
1502+
### 5.7 Put Interaction Logic in Event Handlers
1503+
1504+
**Impact: MEDIUM (avoids effect re-runs and duplicate side effects)**
1505+
1506+
If a side effect is triggered by a specific user action (submit, click, drag), run it in that event handler. Do not model the action as state + effect; it makes effects re-run on unrelated changes and can duplicate the action.
1507+
1508+
**Incorrect: event modeled as state + effect**
1509+
1510+
```tsx
1511+
function Form() {
1512+
const [submitted, setSubmitted] = useState(false)
1513+
const theme = useContext(ThemeContext)
1514+
1515+
useEffect(() => {
1516+
if (submitted) {
1517+
post('/api/register')
1518+
showToast('Registered', theme)
1519+
}
1520+
}, [submitted, theme])
1521+
1522+
return <button onClick={() => setSubmitted(true)}>Submit</button>
1523+
}
1524+
```
1525+
1526+
**Correct: do it in the handler**
1527+
1528+
```tsx
1529+
function Form() {
1530+
const theme = useContext(ThemeContext)
1531+
1532+
function handleSubmit() {
1533+
post('/api/register')
1534+
showToast('Registered', theme)
1535+
}
1536+
1537+
return <button onClick={handleSubmit}>Submit</button>
1538+
}
1539+
```
1540+
1541+
Reference: [https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler)
1542+
1543+
### 5.8 Subscribe to Derived State
14621544
14631545
**Impact: MEDIUM (reduces re-render frequency)**
14641546
@@ -1483,7 +1565,7 @@ function Sidebar() {
14831565
}
14841566
```
14851567
1486-
### 5.7 Use Functional setState Updates
1568+
### 5.9 Use Functional setState Updates
14871569
14881570
**Impact: MEDIUM (prevents stale closures and unnecessary callback recreations)**
14891571
@@ -1561,7 +1643,7 @@ function TodoList() {
15611643
15621644
**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.
15631645
1564-
### 5.8 Use Lazy State Initialization
1646+
### 5.10 Use Lazy State Initialization
15651647
15661648
**Impact: MEDIUM (wasted computation on every render)**
15671649
@@ -1615,7 +1697,7 @@ Use lazy initialization when computing initial values from localStorage/sessionS
16151697
16161698
For simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary.
16171699
1618-
### 5.9 Use Transitions for Non-Urgent Updates
1700+
### 5.11 Use Transitions for Non-Urgent Updates
16191701
16201702
**Impact: MEDIUM (maintains UI responsiveness)**
16211703
@@ -1651,6 +1733,75 @@ function ScrollTracker() {
16511733
}
16521734
```
16531735
1736+
### 5.12 Use useRef for Transient Values
1737+
1738+
**Impact: MEDIUM (avoids unnecessary re-renders on frequent updates)**
1739+
1740+
When a value changes frequently and you don't want a re-render on every update (e.g., mouse trackers, intervals, transient flags), store it in `useRef` instead of `useState`. Keep component state for UI; use refs for temporary DOM-adjacent values. Updating a ref does not trigger a re-render.
1741+
1742+
**Incorrect: renders every update**
1743+
1744+
```tsx
1745+
function Tracker() {
1746+
const [lastX, setLastX] = useState(0)
1747+
1748+
useEffect(() => {
1749+
const onMove = (e: MouseEvent) => setLastX(e.clientX)
1750+
window.addEventListener('mousemove', onMove)
1751+
return () => window.removeEventListener('mousemove', onMove)
1752+
}, [])
1753+
1754+
return (
1755+
<div
1756+
style={{
1757+
position: 'fixed',
1758+
top: 0,
1759+
left: lastX,
1760+
width: 8,
1761+
height: 8,
1762+
background: 'black',
1763+
}}
1764+
/>
1765+
)
1766+
}
1767+
```
1768+
1769+
**Correct: no re-render for tracking**
1770+
1771+
```tsx
1772+
function Tracker() {
1773+
const lastXRef = useRef(0)
1774+
const dotRef = useRef<HTMLDivElement>(null)
1775+
1776+
useEffect(() => {
1777+
const onMove = (e: MouseEvent) => {
1778+
lastXRef.current = e.clientX
1779+
const node = dotRef.current
1780+
if (node) {
1781+
node.style.transform = `translateX(${e.clientX}px)`
1782+
}
1783+
}
1784+
window.addEventListener('mousemove', onMove)
1785+
return () => window.removeEventListener('mousemove', onMove)
1786+
}, [])
1787+
1788+
return (
1789+
<div
1790+
ref={dotRef}
1791+
style={{
1792+
position: 'fixed',
1793+
top: 0,
1794+
left: 0,
1795+
width: 8,
1796+
height: 8,
1797+
background: 'black',
1798+
transform: 'translateX(0px)',
1799+
}}
1800+
/>
1801+
)
1802+
}
1803+
```
1804+
16541805
---
16551806
16561807
## 6. Rendering Performance
@@ -1880,7 +2031,33 @@ The inline script executes synchronously before showing the element, ensuring th
18802031
18812032
This pattern is especially useful for theme toggles, user preferences, authentication states, and any client-only data that should render immediately without flashing default values.
18822033
1883-
### 6.6 Use Activity Component for Show/Hide
2034+
### 6.6 Suppress Expected Hydration Mismatches
2035+
2036+
**Impact: LOW-MEDIUM (avoids noisy hydration warnings for known differences)**
2037+
2038+
In SSR frameworks (e.g., Next.js), some values are intentionally different on server vs client (random IDs, dates, locale/timezone formatting). For these *expected* mismatches, wrap the dynamic text in an element with `suppressHydrationWarning` to prevent noisy warnings. Do not use this to hide real bugs. Don’t overuse it.
2039+
2040+
**Incorrect: known mismatch warnings**
2041+
2042+
```tsx
2043+
function Timestamp() {
2044+
return <span>{new Date().toLocaleString()}</span>
2045+
}
2046+
```
2047+
2048+
**Correct: suppress expected mismatch only**
2049+
2050+
```tsx
2051+
function Timestamp() {
2052+
return (
2053+
<span suppressHydrationWarning>
2054+
{new Date().toLocaleString()}
2055+
</span>
2056+
)
2057+
}
2058+
```
2059+
2060+
### 6.7 Use Activity Component for Show/Hide
18842061
18852062
**Impact: MEDIUM (preserves state/DOM)**
18862063
@@ -1902,7 +2079,7 @@ function Dropdown({ isOpen }: Props) {
19022079
19032080
Avoids expensive re-renders and state loss.
19042081
1905-
### 6.7 Use Explicit Conditional Rendering
2082+
### 6.8 Use Explicit Conditional Rendering
19062083
19072084
**Impact: LOW (prevents rendering 0 or NaN)**
19082085
@@ -1938,7 +2115,7 @@ function Badge({ count }: { count: number }) {
19382115
// When count = 5, renders: <div><span class="badge">5</span></div>
19392116
```
19402117
1941-
### 6.8 Use useTransition Over Manual Loading States
2118+
### 6.9 Use useTransition Over Manual Loading States
19422119
19432120
**Impact: LOW (reduces re-renders and improves code clarity)**
19442121
@@ -2635,7 +2812,45 @@ const sorted = [...items].sort((a, b) => a.value - b.value)
26352812
26362813
Advanced patterns for specific cases that require careful implementation.
26372814
2638-
### 8.1 Store Event Handlers in Refs
2815+
### 8.1 Initialize App Once, Not Per Mount
2816+
2817+
**Impact: LOW-MEDIUM (avoids duplicate init in development)**
2818+
2819+
Do not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.
2820+
2821+
**Incorrect: runs twice in dev, re-runs on remount**
2822+
2823+
```tsx
2824+
function Comp() {
2825+
useEffect(() => {
2826+
loadFromStorage()
2827+
checkAuthToken()
2828+
}, [])
2829+
2830+
// ...
2831+
}
2832+
```
2833+
2834+
**Correct: once per app load**
2835+
2836+
```tsx
2837+
let didInit = false
2838+
2839+
function Comp() {
2840+
useEffect(() => {
2841+
if (didInit) return
2842+
didInit = true
2843+
loadFromStorage()
2844+
checkAuthToken()
2845+
}, [])
2846+
2847+
// ...
2848+
}
2849+
```
2850+
2851+
Reference: [https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)
2852+
2853+
### 8.2 Store Event Handlers in Refs
26392854
26402855
**Impact: LOW (stable subscriptions)**
26412856
@@ -2671,7 +2886,7 @@ function useWindowEvent(event: string, handler: (e) => void) {
26712886
26722887
`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.
26732888
2674-
### 8.2 useEffectEvent for Stable Callback Refs
2889+
### 8.3 useEffectEvent for Stable Callback Refs
26752890
26762891
**Impact: LOW (prevents effect re-runs)**
26772892

0 commit comments

Comments
 (0)