@jgamaraalv/ts-dev-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +24 -0
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/agents/accessibility-pro.md +139 -0
- package/agents/api-builder.md +110 -0
- package/agents/code-reviewer.md +190 -0
- package/agents/database-expert.md +138 -0
- package/agents/debugger.md +241 -0
- package/agents/docker-expert.md +51 -0
- package/agents/multi-agent-coordinator.md +378 -0
- package/agents/nextjs-expert.md +136 -0
- package/agents/performance-engineer.md +138 -0
- package/agents/playwright-expert.md +126 -0
- package/agents/react-specialist.md +97 -0
- package/agents/security-scanner.md +105 -0
- package/agents/test-generator.md +221 -0
- package/agents/typescript-pro.md +253 -0
- package/agents/ux-optimizer.md +93 -0
- package/docs/rules/orchestration.md.template +126 -0
- package/package.json +28 -0
- package/skills/bullmq/SKILL.md +225 -0
- package/skills/bullmq/references/flows-and-schedulers.md +186 -0
- package/skills/bullmq/references/job-types-and-options.md +163 -0
- package/skills/bullmq/references/patterns.md +273 -0
- package/skills/bullmq/references/production.md +308 -0
- package/skills/composition-patterns/SKILL.md +58 -0
- package/skills/composition-patterns/references/architecture-avoid-boolean-props.md +87 -0
- package/skills/composition-patterns/references/architecture-compound-components.md +107 -0
- package/skills/composition-patterns/references/patterns-children-over-render-props.md +77 -0
- package/skills/composition-patterns/references/patterns-explicit-variants.md +87 -0
- package/skills/composition-patterns/references/react19-no-forwardref.md +37 -0
- package/skills/composition-patterns/references/state-context-interface.md +194 -0
- package/skills/composition-patterns/references/state-decouple-implementation.md +96 -0
- package/skills/composition-patterns/references/state-lift-state.md +126 -0
- package/skills/conventional-commits/SKILL.md +148 -0
- package/skills/docker/SKILL.md +55 -0
- package/skills/docker/references/compose-configs.md +95 -0
- package/skills/docker/references/monorepo-dockerfile.md +111 -0
- package/skills/drizzle-pg/SKILL.md +202 -0
- package/skills/drizzle-pg/references/advanced.md +299 -0
- package/skills/drizzle-pg/references/migrations.md +214 -0
- package/skills/drizzle-pg/references/queries.md +321 -0
- package/skills/drizzle-pg/references/relations.md +272 -0
- package/skills/drizzle-pg/references/schema-pg.md +256 -0
- package/skills/drizzle-pg/references/sql-operator.md +215 -0
- package/skills/fastify-best-practices/SKILL.md +143 -0
- package/skills/fastify-best-practices/references/hooks-and-lifecycle.md +122 -0
- package/skills/fastify-best-practices/references/plugins-and-encapsulation.md +137 -0
- package/skills/fastify-best-practices/references/request-reply-errors.md +189 -0
- package/skills/fastify-best-practices/references/routes-and-handlers.md +134 -0
- package/skills/fastify-best-practices/references/server-and-options.md +127 -0
- package/skills/fastify-best-practices/references/typescript-and-logging.md +223 -0
- package/skills/fastify-best-practices/references/validation-and-serialization.md +190 -0
- package/skills/ioredis/SKILL.md +51 -0
- package/skills/ioredis/references/advanced-patterns.md +312 -0
- package/skills/ioredis/references/cluster-sentinel.md +280 -0
- package/skills/ioredis/references/connection-options.md +187 -0
- package/skills/ioredis/references/core-api.md +179 -0
- package/skills/nextjs-best-practices/SKILL.md +194 -0
- package/skills/nextjs-best-practices/references/async-patterns.md +84 -0
- package/skills/nextjs-best-practices/references/bundling.md +192 -0
- package/skills/nextjs-best-practices/references/data-patterns.md +310 -0
- package/skills/nextjs-best-practices/references/debug-tricks.md +127 -0
- package/skills/nextjs-best-practices/references/directives.md +74 -0
- package/skills/nextjs-best-practices/references/error-handling.md +237 -0
- package/skills/nextjs-best-practices/references/file-conventions.md +152 -0
- package/skills/nextjs-best-practices/references/font.md +175 -0
- package/skills/nextjs-best-practices/references/functions.md +116 -0
- package/skills/nextjs-best-practices/references/hydration-error.md +86 -0
- package/skills/nextjs-best-practices/references/image.md +184 -0
- package/skills/nextjs-best-practices/references/metadata.md +305 -0
- package/skills/nextjs-best-practices/references/parallel-routes.md +299 -0
- package/skills/nextjs-best-practices/references/route-handlers.md +154 -0
- package/skills/nextjs-best-practices/references/rsc-boundaries.md +168 -0
- package/skills/nextjs-best-practices/references/runtime-selection.md +40 -0
- package/skills/nextjs-best-practices/references/scripts.md +148 -0
- package/skills/nextjs-best-practices/references/self-hosting.md +210 -0
- package/skills/nextjs-best-practices/references/suspense-boundaries.md +67 -0
- package/skills/owasp-security-review/SKILL.md +98 -0
- package/skills/owasp-security-review/references/a01-broken-access-control.md +78 -0
- package/skills/owasp-security-review/references/a02-security-misconfiguration.md +81 -0
- package/skills/owasp-security-review/references/a03-supply-chain-failures.md +65 -0
- package/skills/owasp-security-review/references/a04-cryptographic-failures.md +82 -0
- package/skills/owasp-security-review/references/a05-injection.md +106 -0
- package/skills/owasp-security-review/references/a06-insecure-design.md +76 -0
- package/skills/owasp-security-review/references/a07-authentication-failures.md +83 -0
- package/skills/owasp-security-review/references/a08-integrity-failures.md +72 -0
- package/skills/owasp-security-review/references/a09-logging-alerting-failures.md +76 -0
- package/skills/owasp-security-review/references/a10-exceptional-conditions.md +131 -0
- package/skills/postgresql/SKILL.md +50 -0
- package/skills/postgresql/references/ddl-schema.md +300 -0
- package/skills/postgresql/references/indexes.md +257 -0
- package/skills/postgresql/references/jsonb.md +261 -0
- package/skills/postgresql/references/performance.md +291 -0
- package/skills/postgresql/references/psql-cli.md +153 -0
- package/skills/postgresql/references/queries.md +287 -0
- package/skills/postgresql/references/transactions.md +280 -0
- package/skills/react-best-practices/SKILL.md +110 -0
- package/skills/react-best-practices/references/advanced-patterns.md +91 -0
- package/skills/react-best-practices/references/async-patterns.md +233 -0
- package/skills/react-best-practices/references/bundle-optimization.md +201 -0
- package/skills/react-best-practices/references/client-patterns.md +178 -0
- package/skills/react-best-practices/references/js-performance.md +210 -0
- package/skills/react-best-practices/references/rendering-performance.md +209 -0
- package/skills/react-best-practices/references/rerender-optimization.md +316 -0
- package/skills/react-best-practices/references/server-performance.md +274 -0
- package/skills/service-worker/SKILL.md +195 -0
- package/skills/service-worker/references/api-reference.md +114 -0
- package/skills/service-worker/references/caching-strategies.md +202 -0
- package/skills/service-worker/references/push-and-sync.md +261 -0
- package/skills/typescript-conventions/SKILL.md +51 -0
- package/skills/ui-ux-guidelines/SKILL.md +105 -0
- package/skills/ui-ux-guidelines/references/accessibility-and-interaction.md +74 -0
- package/skills/ui-ux-guidelines/references/forms-content-checklist.md +126 -0
- package/skills/ui-ux-guidelines/references/layout-typography-animation.md +95 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# JavaScript Performance
|
|
2
|
+
|
|
3
|
+
Low-level JS optimizations for DOM manipulation, caching, and data structures.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Avoid Layout Thrashing](#avoid-layout-thrashing)
|
|
8
|
+
- [Cache Repeated Function Calls](#cache-repeated-function-calls)
|
|
9
|
+
- [Cache Storage API Calls](#cache-storage-api-calls)
|
|
10
|
+
- [Build Index Maps for Repeated Lookups](#build-index-maps-for-repeated-lookups)
|
|
11
|
+
- [Use Loop for Min/Max Instead of Sort](#use-loop-for-minmax-instead-of-sort)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Avoid Layout Thrashing
|
|
16
|
+
|
|
17
|
+
Avoid interleaving style writes with layout reads. Reading layout properties (`offsetWidth`, `getBoundingClientRect()`, `getComputedStyle()`) between style changes forces a synchronous reflow.
|
|
18
|
+
|
|
19
|
+
**Incorrect (interleaved reads and writes):**
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
function layoutThrashing(element: HTMLElement) {
|
|
23
|
+
element.style.width = "100px";
|
|
24
|
+
const width = element.offsetWidth; // Forces reflow
|
|
25
|
+
element.style.height = "200px";
|
|
26
|
+
const height = element.offsetHeight; // Forces another reflow
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Correct (batch writes, then read once):**
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
function updateElementStyles(element: HTMLElement) {
|
|
34
|
+
element.style.width = "100px";
|
|
35
|
+
element.style.height = "200px";
|
|
36
|
+
element.style.backgroundColor = "blue";
|
|
37
|
+
element.style.border = "1px solid black";
|
|
38
|
+
|
|
39
|
+
const { width, height } = element.getBoundingClientRect();
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Better: use CSS classes:**
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
element.classList.add("highlighted-box");
|
|
47
|
+
const { width, height } = element.getBoundingClientRect();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**React example:**
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// Incorrect
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
ref.current.style.width = "100px";
|
|
56
|
+
const width = ref.current.offsetWidth; // Forces layout
|
|
57
|
+
ref.current.style.height = "200px";
|
|
58
|
+
}, [isHighlighted]);
|
|
59
|
+
|
|
60
|
+
// Correct: toggle class
|
|
61
|
+
<div className={isHighlighted ? "highlighted-box" : ""}>Content</div>;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Prefer CSS classes over inline styles. See [layout-forcing properties gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) for more information.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Cache Repeated Function Calls
|
|
69
|
+
|
|
70
|
+
Use a module-level Map to cache function results when the same function is called repeatedly with the same inputs.
|
|
71
|
+
|
|
72
|
+
**Incorrect (redundant computation):**
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
function ProjectList({ projects }: { projects: Project[] }) {
|
|
76
|
+
return (
|
|
77
|
+
<div>
|
|
78
|
+
{projects.map((project) => {
|
|
79
|
+
const slug = slugify(project.name); // Called 100+ times for same names
|
|
80
|
+
return <ProjectCard key={project.id} slug={slug} />;
|
|
81
|
+
})}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Correct (cached results):**
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const slugifyCache = new Map<string, string>();
|
|
91
|
+
|
|
92
|
+
function cachedSlugify(text: string): string {
|
|
93
|
+
if (slugifyCache.has(text)) return slugifyCache.get(text)!;
|
|
94
|
+
const result = slugify(text);
|
|
95
|
+
slugifyCache.set(text, result);
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
|
|
101
|
+
|
|
102
|
+
Reference: [How we made the Vercel Dashboard twice as fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Cache Storage API Calls
|
|
107
|
+
|
|
108
|
+
`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.
|
|
109
|
+
|
|
110
|
+
**Incorrect (reads storage on every call):**
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
function getTheme() {
|
|
114
|
+
return localStorage.getItem("theme") ?? "light";
|
|
115
|
+
}
|
|
116
|
+
// Called 10 times = 10 storage reads
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Correct (Map cache):**
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const storageCache = new Map<string, string | null>();
|
|
123
|
+
|
|
124
|
+
function getLocalStorage(key: string) {
|
|
125
|
+
if (!storageCache.has(key)) {
|
|
126
|
+
storageCache.set(key, localStorage.getItem(key));
|
|
127
|
+
}
|
|
128
|
+
return storageCache.get(key);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function setLocalStorage(key: string, value: string) {
|
|
132
|
+
localStorage.setItem(key, value);
|
|
133
|
+
storageCache.set(key, value);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Invalidate on external changes:**
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
window.addEventListener("storage", (e) => {
|
|
141
|
+
if (e.key) storageCache.delete(e.key);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
document.addEventListener("visibilitychange", () => {
|
|
145
|
+
if (document.visibilityState === "visible") storageCache.clear();
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Build Index Maps for Repeated Lookups
|
|
152
|
+
|
|
153
|
+
Multiple `.find()` calls by the same key should use a Map.
|
|
154
|
+
|
|
155
|
+
**Incorrect (O(n) per lookup):**
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
function processOrders(orders: Order[], users: User[]) {
|
|
159
|
+
return orders.map((order) => ({
|
|
160
|
+
...order,
|
|
161
|
+
user: users.find((u) => u.id === order.userId),
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Correct (O(1) per lookup):**
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
function processOrders(orders: Order[], users: User[]) {
|
|
170
|
+
const userById = new Map(users.map((u) => [u.id, u]));
|
|
171
|
+
|
|
172
|
+
return orders.map((order) => ({
|
|
173
|
+
...order,
|
|
174
|
+
user: userById.get(order.userId),
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Build map once O(n), then all lookups are O(1). For 1000 orders x 1000 users: 1M ops down to 2K ops.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Use Loop for Min/Max Instead of Sort
|
|
184
|
+
|
|
185
|
+
Finding the smallest or largest element only requires a single pass. Sorting is O(n log n) waste.
|
|
186
|
+
|
|
187
|
+
**Incorrect (O(n log n)):**
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
function getLatestProject(projects: Project[]) {
|
|
191
|
+
const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
192
|
+
return sorted[0];
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Correct (O(n)):**
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
function getLatestProject(projects: Project[]) {
|
|
200
|
+
if (projects.length === 0) return null;
|
|
201
|
+
|
|
202
|
+
let latest = projects[0];
|
|
203
|
+
for (let i = 1; i < projects.length; i++) {
|
|
204
|
+
if (projects[i].updatedAt > latest.updatedAt) latest = projects[i];
|
|
205
|
+
}
|
|
206
|
+
return latest;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`Math.min(...arr)` / `Math.max(...arr)` works for small arrays but throws on very large arrays due to spread operator limitations (~124K in Chrome, ~638K in Safari).
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Rendering Performance
|
|
2
|
+
|
|
3
|
+
Optimize DOM rendering, hydration, and visual transitions.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Animate SVG Wrapper Instead of SVG Element](#animate-svg-wrapper-instead-of-svg-element)
|
|
8
|
+
- [CSS content-visibility for Long Lists](#css-content-visibility-for-long-lists)
|
|
9
|
+
- [Hoist Static JSX Elements](#hoist-static-jsx-elements)
|
|
10
|
+
- [Prevent Hydration Mismatch Without Flickering](#prevent-hydration-mismatch-without-flickering)
|
|
11
|
+
- [Use useTransition Over Manual Loading States](#use-usetransition-over-manual-loading-states)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Animate SVG Wrapper Instead of SVG Element
|
|
16
|
+
|
|
17
|
+
Many browsers don't have hardware acceleration for CSS3 animations on SVG elements. Wrap SVG in a `<div>` and animate the wrapper instead.
|
|
18
|
+
|
|
19
|
+
**Incorrect (no hardware acceleration):**
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
function LoadingSpinner() {
|
|
23
|
+
return (
|
|
24
|
+
<svg className="animate-spin" width="24" height="24" viewBox="0 0 24 24">
|
|
25
|
+
<circle cx="12" cy="12" r="10" stroke="currentColor" />
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Correct (hardware accelerated):**
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
function LoadingSpinner() {
|
|
35
|
+
return (
|
|
36
|
+
<div className="animate-spin">
|
|
37
|
+
<svg width="24" height="24" viewBox="0 0 24 24">
|
|
38
|
+
<circle cx="12" cy="12" r="10" stroke="currentColor" />
|
|
39
|
+
</svg>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Applies to all CSS transforms and transitions (`transform`, `opacity`, `translate`, `scale`, `rotate`).
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## CSS content-visibility for Long Lists
|
|
50
|
+
|
|
51
|
+
Apply `content-visibility: auto` to defer off-screen rendering.
|
|
52
|
+
|
|
53
|
+
```css
|
|
54
|
+
.message-item {
|
|
55
|
+
content-visibility: auto;
|
|
56
|
+
contain-intrinsic-size: 0 80px;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
function MessageList({ messages }: { messages: Message[] }) {
|
|
62
|
+
return (
|
|
63
|
+
<div className="overflow-y-auto h-screen">
|
|
64
|
+
{messages.map((msg) => (
|
|
65
|
+
<div key={msg.id} className="message-item">
|
|
66
|
+
<Avatar user={msg.author} />
|
|
67
|
+
<div>{msg.content}</div>
|
|
68
|
+
</div>
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For 1000 messages, browser skips layout/paint for ~990 off-screen items (10x faster initial render).
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Hoist Static JSX Elements
|
|
80
|
+
|
|
81
|
+
Extract static JSX outside components to avoid re-creation.
|
|
82
|
+
|
|
83
|
+
**Incorrect (recreates element every render):**
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
function LoadingSkeleton() {
|
|
87
|
+
return <div className="animate-pulse h-20 bg-gray-200" />;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function Container() {
|
|
91
|
+
return <div>{loading && <LoadingSkeleton />}</div>;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Correct (reuses same element):**
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
const loadingSkeleton = <div className="animate-pulse h-20 bg-gray-200" />;
|
|
99
|
+
|
|
100
|
+
function Container() {
|
|
101
|
+
return <div>{loading && loadingSkeleton}</div>;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Especially helpful for large and static SVG nodes. React Compiler automatically hoists static JSX when enabled.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Prevent Hydration Mismatch Without Flickering
|
|
110
|
+
|
|
111
|
+
When rendering content that depends on client-side storage (localStorage, cookies), avoid both SSR breakage and post-hydration flickering by injecting a synchronous inline script that updates the DOM before React hydrates.
|
|
112
|
+
|
|
113
|
+
**Incorrect (visual flickering):**
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
function ThemeWrapper({ children }: { children: ReactNode }) {
|
|
117
|
+
const [theme, setTheme] = useState("light");
|
|
118
|
+
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
const stored = localStorage.getItem("theme");
|
|
121
|
+
if (stored) setTheme(stored);
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
return <div className={theme}>{children}</div>;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Correct (no flicker, no hydration mismatch):**
|
|
129
|
+
|
|
130
|
+
Use an inline `<script>` tag with a self-executing function that reads localStorage synchronously and sets the className on the wrapper element before React hydrates. The script runs before the browser paints, ensuring correct initial render.
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
function ThemeWrapper({ children }: { children: ReactNode }) {
|
|
134
|
+
return (
|
|
135
|
+
<>
|
|
136
|
+
<div id="theme-wrapper">{children}</div>
|
|
137
|
+
{/* Inline script reads localStorage and sets className synchronously */}
|
|
138
|
+
<ThemeScript />
|
|
139
|
+
</>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The inline script executes synchronously before showing the element. Useful for theme toggles, user preferences, and authentication states.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Use useTransition Over Manual Loading States
|
|
149
|
+
|
|
150
|
+
Use `useTransition` instead of manual `useState` for loading states. Provides built-in `isPending` state and automatically manages transitions.
|
|
151
|
+
|
|
152
|
+
**Incorrect (manual loading state):**
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
function SearchResults() {
|
|
156
|
+
const [query, setQuery] = useState("");
|
|
157
|
+
const [results, setResults] = useState([]);
|
|
158
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
159
|
+
|
|
160
|
+
const handleSearch = async (value: string) => {
|
|
161
|
+
setIsLoading(true);
|
|
162
|
+
setQuery(value);
|
|
163
|
+
const data = await fetchResults(value);
|
|
164
|
+
setResults(data);
|
|
165
|
+
setIsLoading(false);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<>
|
|
170
|
+
<input onChange={(e) => handleSearch(e.target.value)} />
|
|
171
|
+
{isLoading && <Spinner />}
|
|
172
|
+
<ResultsList results={results} />
|
|
173
|
+
</>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Correct (useTransition):**
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
import { useTransition, useState } from "react";
|
|
182
|
+
|
|
183
|
+
function SearchResults() {
|
|
184
|
+
const [query, setQuery] = useState("");
|
|
185
|
+
const [results, setResults] = useState([]);
|
|
186
|
+
const [isPending, startTransition] = useTransition();
|
|
187
|
+
|
|
188
|
+
const handleSearch = (value: string) => {
|
|
189
|
+
setQuery(value);
|
|
190
|
+
|
|
191
|
+
startTransition(async () => {
|
|
192
|
+
const data = await fetchResults(value);
|
|
193
|
+
setResults(data);
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<>
|
|
199
|
+
<input onChange={(e) => handleSearch(e.target.value)} />
|
|
200
|
+
{isPending && <Spinner />}
|
|
201
|
+
<ResultsList results={results} />
|
|
202
|
+
</>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Benefits: automatic pending state, error resilience, better responsiveness, interrupt handling (new transitions cancel pending ones).
|
|
208
|
+
|
|
209
|
+
Reference: [useTransition](https://react.dev/reference/react/useTransition)
|