memory-journal-mcp 7.0.1 → 7.2.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/README.md +75 -66
- package/dist/{chunk-6J4RPJ4I.js → chunk-GR4T3SRW.js} +146 -105
- package/dist/{chunk-ARLH46WS.js → chunk-IWKLHSPU.js} +89 -3
- package/dist/{chunk-2BJHLTYP.js → chunk-ORV7ZZOE.js} +1086 -86
- package/dist/cli.js +30 -4
- package/dist/github-integration-2TFMXHIJ.js +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +3 -3
- package/dist/{tools-FFFGXIKN.js → tools-CXR2FEB2.js} +2 -2
- package/package.json +2 -2
- package/skills/README.md +77 -0
- package/skills/autonomous-dev/SKILL.md +56 -0
- package/skills/bin/sync.js +50 -0
- package/skills/bun/SKILL.md +156 -0
- package/skills/github-commander/SKILL.md +1 -1
- package/skills/github-commander/workflows/code-quality-audit.md +7 -5
- package/skills/github-commander/workflows/issue-triage.md +13 -4
- package/skills/github-commander/workflows/milestone-sprint.md +9 -1
- package/skills/github-commander/workflows/perf-audit.md +2 -0
- package/skills/github-commander/workflows/pr-review.md +9 -3
- package/skills/github-commander/workflows/roadmap-kickoff.md +79 -0
- package/skills/github-commander/workflows/security-audit.md +3 -3
- package/skills/github-commander/workflows/update-deps.md +2 -2
- package/skills/gitlab/SKILL.md +115 -0
- package/skills/gitlab/package-lock.json +392 -0
- package/skills/gitlab/package.json +14 -0
- package/skills/gitlab/scripts/gitlab-client.ts +125 -0
- package/skills/gitlab/scripts/gitlab-helper.ts +80 -0
- package/skills/golang/SKILL.md +54 -0
- package/skills/mysql/SKILL.md +30 -0
- package/skills/package.json +48 -0
- package/skills/playwright-standard/SKILL.md +58 -0
- package/skills/playwright-standard/examples/fixtures.ts +66 -0
- package/skills/playwright-standard/examples/type-stubs.d.ts +10 -0
- package/skills/playwright-standard/references/advanced-scenarios.md +59 -0
- package/skills/playwright-standard/references/infrastructure.md +43 -0
- package/skills/postgres/SKILL.md +33 -0
- package/skills/react-best-practices/AGENTS.md +2883 -0
- package/skills/react-best-practices/README.md +127 -0
- package/skills/react-best-practices/SKILL.md +138 -0
- package/skills/react-best-practices/metadata.json +17 -0
- package/skills/react-best-practices/rules/_sections.md +46 -0
- package/skills/react-best-practices/rules/_template.md +28 -0
- package/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/skills/react-best-practices/rules/advanced-init-once.md +42 -0
- package/skills/react-best-practices/rules/advanced-use-latest.md +39 -0
- package/skills/react-best-practices/rules/async-api-routes.md +35 -0
- package/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/skills/react-best-practices/rules/async-dependencies.md +48 -0
- package/skills/react-best-practices/rules/async-parallel.md +24 -0
- package/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/skills/react-best-practices/rules/bundle-conditional.md +37 -0
- package/skills/react-best-practices/rules/bundle-defer-third-party.md +48 -0
- package/skills/react-best-practices/rules/bundle-dynamic-imports.md +34 -0
- package/skills/react-best-practices/rules/bundle-preload.md +44 -0
- package/skills/react-best-practices/rules/client-event-listeners.md +78 -0
- package/skills/react-best-practices/rules/client-localstorage-schema.md +74 -0
- package/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/skills/react-best-practices/rules/js-batch-dom-css.md +110 -0
- package/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/skills/react-best-practices/rules/js-cache-storage.md +68 -0
- package/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/skills/react-best-practices/rules/js-length-check-first.md +50 -0
- package/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/skills/react-best-practices/rules/rendering-activity.md +24 -0
- package/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +38 -0
- package/skills/react-best-practices/rules/rendering-conditional-render.md +32 -0
- package/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/skills/react-best-practices/rules/rendering-hoist-jsx.md +36 -0
- package/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +72 -0
- package/skills/react-best-practices/rules/rendering-hydration-suppress-warning.md +26 -0
- package/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/skills/react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/skills/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/skills/react-best-practices/rules/rerender-functional-setstate.md +77 -0
- package/skills/react-best-practices/rules/rerender-lazy-state-init.md +56 -0
- package/skills/react-best-practices/rules/rerender-memo-with-default-value.md +36 -0
- package/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/skills/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/skills/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/skills/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/skills/react-best-practices/rules/server-auth-actions.md +96 -0
- package/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/skills/react-best-practices/rules/server-dedup-props.md +65 -0
- package/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/skills/rust/SKILL.md +86 -0
- package/skills/shadcn-ui/SKILL.md +72 -0
- package/skills/skill-builder/SKILL.md +457 -0
- package/skills/skill-builder/checklist.md +65 -0
- package/skills/sqlite/SKILL.md +38 -0
- package/skills/typescript/SKILL.md +453 -0
- package/skills/typescript/assets/eslint-template.js +102 -0
- package/skills/typescript/assets/tsconfig-template.json +45 -0
- package/skills/typescript/references/enterprise-patterns.md +531 -0
- package/skills/typescript/references/generics.md +493 -0
- package/skills/typescript/references/nestjs-integration.md +579 -0
- package/skills/typescript/references/react-integration.md +616 -0
- package/skills/typescript/references/toolchain.md +547 -0
- package/skills/typescript/references/type-system.md +481 -0
- package/skills/vitest-standard/SKILL.md +82 -0
- package/skills/vitest-standard/examples/service-mock.ts +60 -0
- package/skills/vitest-standard/examples/tdd-calculator.ts +41 -0
- package/skills/vitest-standard/examples/type-stubs.d.ts +18 -0
- package/skills/vitest-standard/references/async-and-errors.md +58 -0
- package/skills/vitest-standard/references/coverage-and-config.md +53 -0
- package/skills/vitest-standard/references/mocking.md +61 -0
- package/skills/vitest-standard/references/tdd-patterns.md +60 -0
- package/dist/github-integration-PDRLXKGM.js +0 -1
- package/skills/github-commander/workflows/full-audit.md +0 -134
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
# React Integration Reference
|
|
2
|
+
|
|
3
|
+
> **Load when:** User asks about React with TypeScript, typed components, hooks, state management, or React patterns.
|
|
4
|
+
|
|
5
|
+
Type-safe React development patterns for React 19+.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
- [Component Patterns](#component-patterns)
|
|
10
|
+
- [Hooks with TypeScript](#hooks-with-typescript)
|
|
11
|
+
- [State Management](#state-management)
|
|
12
|
+
- [Event Handling](#event-handling)
|
|
13
|
+
- [Context API](#context-api)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Component Patterns
|
|
18
|
+
|
|
19
|
+
### Functional Components
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Basic typed component
|
|
23
|
+
interface GreetingProps {
|
|
24
|
+
name: string;
|
|
25
|
+
age?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function Greeting({ name, age }: GreetingProps) {
|
|
29
|
+
return (
|
|
30
|
+
<div>
|
|
31
|
+
Hello, {name}!
|
|
32
|
+
{age && <span> You are {age} years old.</span>}
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// With React.FC (optional, some prefer explicit return type)
|
|
38
|
+
const GreetingFC: React.FC<GreetingProps> = ({ name, age }) => {
|
|
39
|
+
return <div>Hello, {name}!</div>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Component with children
|
|
43
|
+
interface CardProps {
|
|
44
|
+
title: string;
|
|
45
|
+
children: React.ReactNode;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function Card({ title, children }: CardProps) {
|
|
49
|
+
return (
|
|
50
|
+
<div className="card">
|
|
51
|
+
<h2>{title}</h2>
|
|
52
|
+
<div className="card-body">{children}</div>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Generic Components
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Generic list component
|
|
62
|
+
interface ListProps<T> {
|
|
63
|
+
items: T[];
|
|
64
|
+
renderItem: (item: T, index: number) => React.ReactNode;
|
|
65
|
+
keyExtractor: (item: T) => string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
|
|
69
|
+
return (
|
|
70
|
+
<ul>
|
|
71
|
+
{items.map((item, index) => (
|
|
72
|
+
<li key={keyExtractor(item)}>{renderItem(item, index)}</li>
|
|
73
|
+
))}
|
|
74
|
+
</ul>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Usage
|
|
79
|
+
interface User {
|
|
80
|
+
id: string;
|
|
81
|
+
name: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
<List<User>
|
|
85
|
+
items={users}
|
|
86
|
+
keyExtractor={(user) => user.id}
|
|
87
|
+
renderItem={(user) => <span>{user.name}</span>}
|
|
88
|
+
/>;
|
|
89
|
+
|
|
90
|
+
// Generic select component
|
|
91
|
+
interface SelectProps<T> {
|
|
92
|
+
options: T[];
|
|
93
|
+
value: T | null;
|
|
94
|
+
onChange: (value: T) => void;
|
|
95
|
+
getLabel: (option: T) => string;
|
|
96
|
+
getValue: (option: T) => string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function Select<T>({
|
|
100
|
+
options,
|
|
101
|
+
value,
|
|
102
|
+
onChange,
|
|
103
|
+
getLabel,
|
|
104
|
+
getValue
|
|
105
|
+
}: SelectProps<T>) {
|
|
106
|
+
return (
|
|
107
|
+
<select
|
|
108
|
+
value={value ? getValue(value) : ""}
|
|
109
|
+
onChange={(e) => {
|
|
110
|
+
const selected = options.find((opt) => getValue(opt) === e.target.value);
|
|
111
|
+
if (selected) onChange(selected);
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
<option value="">Select...</option>
|
|
115
|
+
{options.map((opt) => (
|
|
116
|
+
<option key={getValue(opt)} value={getValue(opt)}>
|
|
117
|
+
{getLabel(opt)}
|
|
118
|
+
</option>
|
|
119
|
+
))}
|
|
120
|
+
</select>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Polymorphic Components
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// Component that can render as different elements
|
|
129
|
+
type ButtonProps<T extends React.ElementType> = {
|
|
130
|
+
as?: T;
|
|
131
|
+
children: React.ReactNode;
|
|
132
|
+
variant?: "primary" | "secondary";
|
|
133
|
+
} & Omit<React.ComponentPropsWithoutRef<T>, "as" | "children">;
|
|
134
|
+
|
|
135
|
+
function Button<T extends React.ElementType = "button">({
|
|
136
|
+
as,
|
|
137
|
+
children,
|
|
138
|
+
variant = "primary",
|
|
139
|
+
...props
|
|
140
|
+
}: ButtonProps<T>) {
|
|
141
|
+
const Component = as || "button";
|
|
142
|
+
return (
|
|
143
|
+
<Component className={`btn btn-${variant}`} {...props}>
|
|
144
|
+
{children}
|
|
145
|
+
</Component>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Usage
|
|
150
|
+
<Button>Click me</Button>
|
|
151
|
+
<Button as="a" href="/about">Link Button</Button>
|
|
152
|
+
<Button as={Link} to="/home">Router Link</Button>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Hooks with TypeScript
|
|
158
|
+
|
|
159
|
+
### useState
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// Basic usage (type inferred)
|
|
163
|
+
const [count, setCount] = useState(0)
|
|
164
|
+
|
|
165
|
+
// Explicit type (for complex types or initial null)
|
|
166
|
+
const [user, setUser] = useState<User | null>(null)
|
|
167
|
+
|
|
168
|
+
// With union types
|
|
169
|
+
type Status = 'idle' | 'loading' | 'success' | 'error'
|
|
170
|
+
const [status, setStatus] = useState<Status>('idle')
|
|
171
|
+
|
|
172
|
+
// Lazy initialization
|
|
173
|
+
const [state, setState] = useState<ExpensiveState>(() => {
|
|
174
|
+
return computeExpensiveInitialState()
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### useRef
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// DOM element ref
|
|
182
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
183
|
+
|
|
184
|
+
function focusInput() {
|
|
185
|
+
inputRef.current?.focus()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Mutable ref (no initial render)
|
|
189
|
+
const intervalRef = useRef<NodeJS.Timeout | null>(null)
|
|
190
|
+
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
intervalRef.current = setInterval(() => {}, 1000)
|
|
193
|
+
return () => {
|
|
194
|
+
if (intervalRef.current) {
|
|
195
|
+
clearInterval(intervalRef.current)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}, [])
|
|
199
|
+
|
|
200
|
+
// Ref to store previous value
|
|
201
|
+
function usePrevious<T>(value: T): T | undefined {
|
|
202
|
+
const ref = useRef<T | undefined>(undefined)
|
|
203
|
+
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
ref.current = value
|
|
206
|
+
}, [value])
|
|
207
|
+
|
|
208
|
+
return ref.current
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### useReducer
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// Define state and actions
|
|
216
|
+
interface CounterState {
|
|
217
|
+
count: number
|
|
218
|
+
step: number
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
type CounterAction =
|
|
222
|
+
| { type: 'increment' }
|
|
223
|
+
| { type: 'decrement' }
|
|
224
|
+
| { type: 'setStep'; payload: number }
|
|
225
|
+
| { type: 'reset' }
|
|
226
|
+
|
|
227
|
+
function counterReducer(state: CounterState, action: CounterAction): CounterState {
|
|
228
|
+
switch (action.type) {
|
|
229
|
+
case 'increment':
|
|
230
|
+
return { ...state, count: state.count + state.step }
|
|
231
|
+
case 'decrement':
|
|
232
|
+
return { ...state, count: state.count - state.step }
|
|
233
|
+
case 'setStep':
|
|
234
|
+
return { ...state, step: action.payload }
|
|
235
|
+
case 'reset':
|
|
236
|
+
return { count: 0, step: 1 }
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Usage
|
|
241
|
+
const [state, dispatch] = useReducer(counterReducer, { count: 0, step: 1 })
|
|
242
|
+
|
|
243
|
+
dispatch({ type: 'increment' })
|
|
244
|
+
dispatch({ type: 'setStep', payload: 5 })
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Custom Hooks
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// Async data fetching hook
|
|
251
|
+
interface UseAsyncResult<T> {
|
|
252
|
+
data: T | null
|
|
253
|
+
loading: boolean
|
|
254
|
+
error: Error | null
|
|
255
|
+
refetch: () => void
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function useAsync<T>(
|
|
259
|
+
asyncFn: () => Promise<T>,
|
|
260
|
+
deps: React.DependencyList = []
|
|
261
|
+
): UseAsyncResult<T> {
|
|
262
|
+
const [data, setData] = useState<T | null>(null)
|
|
263
|
+
const [loading, setLoading] = useState(true)
|
|
264
|
+
const [error, setError] = useState<Error | null>(null)
|
|
265
|
+
|
|
266
|
+
const execute = useCallback(async () => {
|
|
267
|
+
setLoading(true)
|
|
268
|
+
setError(null)
|
|
269
|
+
try {
|
|
270
|
+
const result = await asyncFn()
|
|
271
|
+
setData(result)
|
|
272
|
+
} catch (e) {
|
|
273
|
+
setError(e instanceof Error ? e : new Error(String(e)))
|
|
274
|
+
} finally {
|
|
275
|
+
setLoading(false)
|
|
276
|
+
}
|
|
277
|
+
}, deps)
|
|
278
|
+
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
execute()
|
|
281
|
+
}, [execute])
|
|
282
|
+
|
|
283
|
+
return { data, loading, error, refetch: execute }
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Usage
|
|
287
|
+
const {
|
|
288
|
+
data: users,
|
|
289
|
+
loading,
|
|
290
|
+
error,
|
|
291
|
+
} = useAsync(() => fetch('/api/users').then((r) => r.json()), [])
|
|
292
|
+
|
|
293
|
+
// Local storage hook
|
|
294
|
+
function useLocalStorage<T>(
|
|
295
|
+
key: string,
|
|
296
|
+
initialValue: T
|
|
297
|
+
): [T, (value: T | ((prev: T) => T)) => void] {
|
|
298
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
299
|
+
try {
|
|
300
|
+
const item = localStorage.getItem(key)
|
|
301
|
+
return item ? JSON.parse(item) : initialValue
|
|
302
|
+
} catch {
|
|
303
|
+
return initialValue
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
const setValue = (value: T | ((prev: T) => T)) => {
|
|
308
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value
|
|
309
|
+
setStoredValue(valueToStore)
|
|
310
|
+
localStorage.setItem(key, JSON.stringify(valueToStore))
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return [storedValue, setValue]
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## State Management
|
|
320
|
+
|
|
321
|
+
### Zustand with TypeScript
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { create } from 'zustand'
|
|
325
|
+
import { devtools, persist } from 'zustand/middleware'
|
|
326
|
+
|
|
327
|
+
// Define state interface
|
|
328
|
+
interface AuthState {
|
|
329
|
+
user: User | null
|
|
330
|
+
token: string | null
|
|
331
|
+
isAuthenticated: boolean
|
|
332
|
+
login: (email: string, password: string) => Promise<void>
|
|
333
|
+
logout: () => void
|
|
334
|
+
setUser: (user: User) => void
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Create typed store
|
|
338
|
+
const useAuthStore = create<AuthState>()(
|
|
339
|
+
devtools(
|
|
340
|
+
persist(
|
|
341
|
+
(set) => ({
|
|
342
|
+
user: null,
|
|
343
|
+
token: null,
|
|
344
|
+
isAuthenticated: false,
|
|
345
|
+
|
|
346
|
+
login: async (email, password) => {
|
|
347
|
+
const response = await api.login(email, password)
|
|
348
|
+
set({
|
|
349
|
+
user: response.user,
|
|
350
|
+
token: response.token,
|
|
351
|
+
isAuthenticated: true,
|
|
352
|
+
})
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
logout: () => {
|
|
356
|
+
set({ user: null, token: null, isAuthenticated: false })
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
setUser: (user) => set({ user }),
|
|
360
|
+
}),
|
|
361
|
+
{ name: 'auth-storage' }
|
|
362
|
+
)
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
// Usage with selectors
|
|
367
|
+
const user = useAuthStore((state) => state.user)
|
|
368
|
+
const login = useAuthStore((state) => state.login)
|
|
369
|
+
|
|
370
|
+
// Shallow comparison for multiple values
|
|
371
|
+
import { useShallow } from 'zustand/react/shallow'
|
|
372
|
+
|
|
373
|
+
const { user, isAuthenticated } = useAuthStore(
|
|
374
|
+
useShallow((state) => ({
|
|
375
|
+
user: state.user,
|
|
376
|
+
isAuthenticated: state.isAuthenticated,
|
|
377
|
+
}))
|
|
378
|
+
)
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Redux Toolkit with TypeScript
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import { createSlice, PayloadAction, configureStore } from '@reduxjs/toolkit'
|
|
385
|
+
|
|
386
|
+
// Define slice state
|
|
387
|
+
interface TodosState {
|
|
388
|
+
items: Todo[]
|
|
389
|
+
filter: 'all' | 'active' | 'completed'
|
|
390
|
+
loading: boolean
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const initialState: TodosState = {
|
|
394
|
+
items: [],
|
|
395
|
+
filter: 'all',
|
|
396
|
+
loading: false,
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Create typed slice
|
|
400
|
+
const todosSlice = createSlice({
|
|
401
|
+
name: 'todos',
|
|
402
|
+
initialState,
|
|
403
|
+
reducers: {
|
|
404
|
+
addTodo: (state, action: PayloadAction<string>) => {
|
|
405
|
+
state.items.push({
|
|
406
|
+
id: crypto.randomUUID(),
|
|
407
|
+
text: action.payload,
|
|
408
|
+
completed: false,
|
|
409
|
+
})
|
|
410
|
+
},
|
|
411
|
+
toggleTodo: (state, action: PayloadAction<string>) => {
|
|
412
|
+
const todo = state.items.find((t) => t.id === action.payload)
|
|
413
|
+
if (todo) {
|
|
414
|
+
todo.completed = !todo.completed
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
setFilter: (state, action: PayloadAction<TodosState['filter']>) => {
|
|
418
|
+
state.filter = action.payload
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
// Configure store with type inference
|
|
424
|
+
const store = configureStore({
|
|
425
|
+
reducer: {
|
|
426
|
+
todos: todosSlice.reducer,
|
|
427
|
+
},
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
// Infer types from store
|
|
431
|
+
type RootState = ReturnType<typeof store.getState>
|
|
432
|
+
type AppDispatch = typeof store.dispatch
|
|
433
|
+
|
|
434
|
+
// Typed hooks
|
|
435
|
+
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux'
|
|
436
|
+
|
|
437
|
+
const useAppDispatch = () => useDispatch<AppDispatch>()
|
|
438
|
+
const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
|
439
|
+
|
|
440
|
+
// Usage
|
|
441
|
+
const todos = useAppSelector((state) => state.todos.items)
|
|
442
|
+
const dispatch = useAppDispatch()
|
|
443
|
+
dispatch(todosSlice.actions.addTodo('New todo'))
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Event Handling
|
|
449
|
+
|
|
450
|
+
### Common Event Types
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
// Click events
|
|
454
|
+
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
|
|
455
|
+
console.log(event.currentTarget.name)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Form events
|
|
459
|
+
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
|
460
|
+
event.preventDefault()
|
|
461
|
+
const formData = new FormData(event.currentTarget)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Input change
|
|
465
|
+
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
|
|
466
|
+
const { name, value, checked, type } = event.target
|
|
467
|
+
const inputValue = type === 'checkbox' ? checked : value
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Keyboard events
|
|
471
|
+
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
|
|
472
|
+
if (event.key === 'Enter') {
|
|
473
|
+
event.preventDefault()
|
|
474
|
+
// submit form
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Focus events
|
|
479
|
+
function handleFocus(event: React.FocusEvent<HTMLInputElement>) {
|
|
480
|
+
event.target.select()
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Drag events
|
|
484
|
+
function handleDrag(event: React.DragEvent<HTMLDivElement>) {
|
|
485
|
+
event.dataTransfer.setData('text/plain', 'dragged data')
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Form with TypeScript
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
interface FormData {
|
|
493
|
+
name: string;
|
|
494
|
+
email: string;
|
|
495
|
+
role: "user" | "admin";
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function RegistrationForm() {
|
|
499
|
+
const [formData, setFormData] = useState<FormData>({
|
|
500
|
+
name: "",
|
|
501
|
+
email: "",
|
|
502
|
+
role: "user"
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const handleChange = (
|
|
506
|
+
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
|
507
|
+
) => {
|
|
508
|
+
const { name, value } = e.target;
|
|
509
|
+
setFormData((prev) => ({ ...prev, [name]: value }));
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
513
|
+
e.preventDefault();
|
|
514
|
+
console.log(formData);
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
return (
|
|
518
|
+
<form onSubmit={handleSubmit}>
|
|
519
|
+
<input
|
|
520
|
+
name="name"
|
|
521
|
+
value={formData.name}
|
|
522
|
+
onChange={handleChange}
|
|
523
|
+
/>
|
|
524
|
+
<input
|
|
525
|
+
name="email"
|
|
526
|
+
type="email"
|
|
527
|
+
value={formData.email}
|
|
528
|
+
onChange={handleChange}
|
|
529
|
+
/>
|
|
530
|
+
<select name="role" value={formData.role} onChange={handleChange}>
|
|
531
|
+
<option value="user">User</option>
|
|
532
|
+
<option value="admin">Admin</option>
|
|
533
|
+
</select>
|
|
534
|
+
<button type="submit">Register</button>
|
|
535
|
+
</form>
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## Context API
|
|
543
|
+
|
|
544
|
+
### Typed Context
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
// Define context type
|
|
548
|
+
interface ThemeContextType {
|
|
549
|
+
theme: "light" | "dark";
|
|
550
|
+
toggleTheme: () => void;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Create context with undefined default
|
|
554
|
+
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
|
555
|
+
|
|
556
|
+
// Provider component
|
|
557
|
+
function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
558
|
+
const [theme, setTheme] = useState<"light" | "dark">("light");
|
|
559
|
+
|
|
560
|
+
const toggleTheme = useCallback(() => {
|
|
561
|
+
setTheme((prev) => (prev === "light" ? "dark" : "light"));
|
|
562
|
+
}, []);
|
|
563
|
+
|
|
564
|
+
const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);
|
|
565
|
+
|
|
566
|
+
return (
|
|
567
|
+
<ThemeContext.Provider value={value}>
|
|
568
|
+
{children}
|
|
569
|
+
</ThemeContext.Provider>
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Custom hook with type safety
|
|
574
|
+
function useTheme(): ThemeContextType {
|
|
575
|
+
const context = useContext(ThemeContext);
|
|
576
|
+
if (context === undefined) {
|
|
577
|
+
throw new Error("useTheme must be used within ThemeProvider");
|
|
578
|
+
}
|
|
579
|
+
return context;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Usage
|
|
583
|
+
function ThemeToggle() {
|
|
584
|
+
const { theme, toggleTheme } = useTheme();
|
|
585
|
+
return <button onClick={toggleTheme}>Current: {theme}</button>;
|
|
586
|
+
}
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Generic Context Factory
|
|
590
|
+
|
|
591
|
+
```typescript
|
|
592
|
+
// Factory function for creating typed contexts
|
|
593
|
+
function createContext<T>(displayName: string) {
|
|
594
|
+
const Context = React.createContext<T | undefined>(undefined)
|
|
595
|
+
Context.displayName = displayName
|
|
596
|
+
|
|
597
|
+
function useContextHook(): T {
|
|
598
|
+
const context = React.useContext(Context)
|
|
599
|
+
if (context === undefined) {
|
|
600
|
+
throw new Error(`use${displayName} must be used within ${displayName}Provider`)
|
|
601
|
+
}
|
|
602
|
+
return context
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return [Context.Provider, useContextHook] as const
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Usage
|
|
609
|
+
interface AuthContextType {
|
|
610
|
+
user: User | null
|
|
611
|
+
login: (credentials: Credentials) => Promise<void>
|
|
612
|
+
logout: () => void
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const [AuthProvider, useAuth] = createContext<AuthContextType>('Auth')
|
|
616
|
+
```
|