@mytechtoday/augment-extensions 0.1.0 → 0.1.2
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/AGENTS.md +83 -3
- package/README.md +6 -5
- package/augment-extensions/coding-standards/python/README.md +44 -0
- package/augment-extensions/coding-standards/python/module.json +26 -0
- package/augment-extensions/coding-standards/python/rules/best-practices.md +232 -0
- package/augment-extensions/coding-standards/python/rules/code-organization.md +220 -0
- package/augment-extensions/coding-standards/python/rules/error-handling.md +221 -0
- package/augment-extensions/coding-standards/python/rules/naming-conventions.md +172 -0
- package/augment-extensions/coding-standards/python/rules/type-hints.md +188 -0
- package/augment-extensions/coding-standards/react/README.md +45 -0
- package/augment-extensions/coding-standards/react/module.json +27 -0
- package/augment-extensions/coding-standards/react/rules/component-patterns.md +214 -0
- package/augment-extensions/coding-standards/react/rules/hooks-best-practices.md +235 -0
- package/augment-extensions/coding-standards/react/rules/performance.md +300 -0
- package/augment-extensions/coding-standards/react/rules/state-management.md +265 -0
- package/augment-extensions/coding-standards/react/rules/typescript-react.md +271 -0
- package/augment-extensions/domain-rules/api-design/README.md +41 -0
- package/augment-extensions/domain-rules/api-design/module.json +27 -0
- package/augment-extensions/domain-rules/api-design/rules/authentication.md +263 -0
- package/augment-extensions/domain-rules/api-design/rules/documentation.md +395 -0
- package/augment-extensions/domain-rules/api-design/rules/error-handling.md +290 -0
- package/augment-extensions/domain-rules/api-design/rules/graphql-api.md +313 -0
- package/augment-extensions/domain-rules/api-design/rules/rest-api.md +214 -0
- package/augment-extensions/domain-rules/api-design/rules/versioning.md +268 -0
- package/augment-extensions/domain-rules/security/README.md +41 -0
- package/augment-extensions/domain-rules/security/module.json +28 -0
- package/augment-extensions/domain-rules/security/rules/authentication-security.md +361 -0
- package/augment-extensions/domain-rules/security/rules/encryption.md +208 -0
- package/augment-extensions/domain-rules/security/rules/input-validation.md +294 -0
- package/augment-extensions/domain-rules/security/rules/owasp-top-10.md +339 -0
- package/augment-extensions/domain-rules/security/rules/secure-coding.md +293 -0
- package/augment-extensions/domain-rules/security/rules/web-security.md +268 -0
- package/augment-extensions/examples/design-patterns/README.md +37 -0
- package/augment-extensions/examples/design-patterns/examples/behavioral-patterns.md +370 -0
- package/augment-extensions/examples/design-patterns/examples/creational-patterns.md +250 -0
- package/augment-extensions/examples/design-patterns/examples/structural-patterns.md +264 -0
- package/augment-extensions/examples/design-patterns/module.json +27 -0
- package/{modules → augment-extensions}/workflows/beads/examples/complete-workflow-example.md +5 -5
- package/{modules → augment-extensions}/workflows/beads/rules/file-format.md +45 -1
- package/{modules → augment-extensions}/workflows/beads/rules/workflow.md +41 -0
- package/{modules → augment-extensions}/workflows/openspec/examples/complete-change-example.md +14 -0
- package/{modules → augment-extensions}/workflows/openspec/rules/spec-format.md +44 -1
- package/{modules → augment-extensions}/workflows/openspec/rules/workflow.md +25 -0
- package/cli/dist/cli.js +69 -1
- package/cli/dist/cli.js.map +1 -1
- package/cli/dist/commands/coord.d.ts +30 -0
- package/cli/dist/commands/coord.d.ts.map +1 -0
- package/cli/dist/commands/coord.js +150 -0
- package/cli/dist/commands/coord.js.map +1 -0
- package/cli/dist/commands/link.js +1 -1
- package/cli/dist/commands/link.js.map +1 -1
- package/cli/dist/commands/list.js +1 -1
- package/cli/dist/commands/list.js.map +1 -1
- package/cli/dist/commands/search.d.ts.map +1 -1
- package/cli/dist/commands/search.js +107 -5
- package/cli/dist/commands/search.js.map +1 -1
- package/cli/dist/commands/show.js +1 -1
- package/cli/dist/commands/show.js.map +1 -1
- package/cli/dist/commands/sync.d.ts +26 -0
- package/cli/dist/commands/sync.d.ts.map +1 -0
- package/cli/dist/commands/sync.js +106 -0
- package/cli/dist/commands/sync.js.map +1 -0
- package/cli/dist/commands/update.d.ts.map +1 -1
- package/cli/dist/commands/update.js +132 -7
- package/cli/dist/commands/update.js.map +1 -1
- package/cli/dist/utils/auto-sync.d.ts +34 -0
- package/cli/dist/utils/auto-sync.d.ts.map +1 -0
- package/cli/dist/utils/auto-sync.js +172 -0
- package/cli/dist/utils/auto-sync.js.map +1 -0
- package/cli/dist/utils/beads-sync.d.ts +51 -0
- package/cli/dist/utils/beads-sync.d.ts.map +1 -0
- package/cli/dist/utils/beads-sync.js +171 -0
- package/cli/dist/utils/beads-sync.js.map +1 -0
- package/cli/dist/utils/coordination-queries.d.ts +79 -0
- package/cli/dist/utils/coordination-queries.d.ts.map +1 -0
- package/cli/dist/utils/coordination-queries.js +155 -0
- package/cli/dist/utils/coordination-queries.js.map +1 -0
- package/cli/dist/utils/file-tracking.d.ts +42 -0
- package/cli/dist/utils/file-tracking.d.ts.map +1 -0
- package/cli/dist/utils/file-tracking.js +155 -0
- package/cli/dist/utils/file-tracking.js.map +1 -0
- package/cli/dist/utils/migrate.d.ts +25 -0
- package/cli/dist/utils/migrate.d.ts.map +1 -0
- package/cli/dist/utils/migrate.js +204 -0
- package/cli/dist/utils/migrate.js.map +1 -0
- package/cli/dist/utils/openspec-sync.d.ts +48 -0
- package/cli/dist/utils/openspec-sync.d.ts.map +1 -0
- package/cli/dist/utils/openspec-sync.js +167 -0
- package/cli/dist/utils/openspec-sync.js.map +1 -0
- package/{MODULES.md → modules.md} +1 -1
- package/package.json +9 -7
- /package/{modules → augment-extensions}/coding-standards/typescript/README.md +0 -0
- /package/{modules → augment-extensions}/coding-standards/typescript/module.json +0 -0
- /package/{modules → augment-extensions}/coding-standards/typescript/rules/naming-conventions.md +0 -0
- /package/{modules → augment-extensions}/workflows/beads/README.md +0 -0
- /package/{modules → augment-extensions}/workflows/beads/module.json +0 -0
- /package/{modules → augment-extensions}/workflows/beads/rules/best-practices.md +0 -0
- /package/{modules → augment-extensions}/workflows/beads/rules/manual-setup.md +0 -0
- /package/{modules → augment-extensions}/workflows/openspec/README.md +0 -0
- /package/{modules → augment-extensions}/workflows/openspec/module.json +0 -0
- /package/{modules → augment-extensions}/workflows/openspec/rules/best-practices.md +0 -0
- /package/{modules → augment-extensions}/workflows/openspec/rules/manual-setup.md +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# React Component Patterns
|
|
2
|
+
|
|
3
|
+
Modern component design patterns for React applications.
|
|
4
|
+
|
|
5
|
+
## Functional Components
|
|
6
|
+
|
|
7
|
+
Always use functional components with hooks (not class components).
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Good - Functional component
|
|
11
|
+
interface UserCardProps {
|
|
12
|
+
name: string;
|
|
13
|
+
email: string;
|
|
14
|
+
onEdit: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const UserCard: React.FC<UserCardProps> = ({ name, email, onEdit }) => {
|
|
18
|
+
return (
|
|
19
|
+
<div className="user-card">
|
|
20
|
+
<h3>{name}</h3>
|
|
21
|
+
<p>{email}</p>
|
|
22
|
+
<button onClick={onEdit}>Edit</button>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Bad - Class component
|
|
28
|
+
class UserCard extends React.Component<UserCardProps> {
|
|
29
|
+
render() {
|
|
30
|
+
return (
|
|
31
|
+
<div className="user-card">
|
|
32
|
+
<h3>{this.props.name}</h3>
|
|
33
|
+
<p>{this.props.email}</p>
|
|
34
|
+
<button onClick={this.props.onEdit}>Edit</button>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Component Composition
|
|
42
|
+
|
|
43
|
+
Break down complex components into smaller, reusable pieces.
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// Good - Composition
|
|
47
|
+
const UserProfile = ({ user }: { user: User }) => (
|
|
48
|
+
<div className="profile">
|
|
49
|
+
<UserAvatar src={user.avatar} />
|
|
50
|
+
<UserInfo name={user.name} email={user.email} />
|
|
51
|
+
<UserActions userId={user.id} />
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Bad - Monolithic component
|
|
56
|
+
const UserProfile = ({ user }: { user: User }) => (
|
|
57
|
+
<div className="profile">
|
|
58
|
+
<div className="avatar">
|
|
59
|
+
<img src={user.avatar} alt={user.name} />
|
|
60
|
+
</div>
|
|
61
|
+
<div className="info">
|
|
62
|
+
<h2>{user.name}</h2>
|
|
63
|
+
<p>{user.email}</p>
|
|
64
|
+
</div>
|
|
65
|
+
<div className="actions">
|
|
66
|
+
<button onClick={() => editUser(user.id)}>Edit</button>
|
|
67
|
+
<button onClick={() => deleteUser(user.id)}>Delete</button>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Container/Presenter Pattern
|
|
74
|
+
|
|
75
|
+
Separate logic from presentation.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// Presenter - Pure UI component
|
|
79
|
+
interface UserListProps {
|
|
80
|
+
users: User[];
|
|
81
|
+
onUserClick: (id: string) => void;
|
|
82
|
+
loading: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const UserList: React.FC<UserListProps> = ({ users, onUserClick, loading }) => {
|
|
86
|
+
if (loading) return <Spinner />;
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<ul>
|
|
90
|
+
{users.map(user => (
|
|
91
|
+
<li key={user.id} onClick={() => onUserClick(user.id)}>
|
|
92
|
+
{user.name}
|
|
93
|
+
</li>
|
|
94
|
+
))}
|
|
95
|
+
</ul>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Container - Logic and data fetching
|
|
100
|
+
const UserListContainer: React.FC = () => {
|
|
101
|
+
const { data: users, isLoading } = useUsers();
|
|
102
|
+
const navigate = useNavigate();
|
|
103
|
+
|
|
104
|
+
const handleUserClick = (id: string) => {
|
|
105
|
+
navigate(`/users/${id}`);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return <UserList users={users} onUserClick={handleUserClick} loading={isLoading} />;
|
|
109
|
+
};
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Compound Components
|
|
113
|
+
|
|
114
|
+
Create components that work together.
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// Compound component pattern
|
|
118
|
+
interface TabsContextValue {
|
|
119
|
+
activeTab: string;
|
|
120
|
+
setActiveTab: (tab: string) => void;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const TabsContext = createContext<TabsContextValue | undefined>(undefined);
|
|
124
|
+
|
|
125
|
+
export const Tabs: React.FC<{ children: React.ReactNode; defaultTab: string }> = ({
|
|
126
|
+
children,
|
|
127
|
+
defaultTab
|
|
128
|
+
}) => {
|
|
129
|
+
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
|
|
133
|
+
<div className="tabs">{children}</div>
|
|
134
|
+
</TabsContext.Provider>
|
|
135
|
+
);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const TabList: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
139
|
+
<div className="tab-list">{children}</div>
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
export const Tab: React.FC<{ id: string; children: React.ReactNode }> = ({ id, children }) => {
|
|
143
|
+
const context = useContext(TabsContext);
|
|
144
|
+
if (!context) throw new Error('Tab must be used within Tabs');
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<button
|
|
148
|
+
className={context.activeTab === id ? 'active' : ''}
|
|
149
|
+
onClick={() => context.setActiveTab(id)}
|
|
150
|
+
>
|
|
151
|
+
{children}
|
|
152
|
+
</button>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const TabPanel: React.FC<{ id: string; children: React.ReactNode }> = ({ id, children }) => {
|
|
157
|
+
const context = useContext(TabsContext);
|
|
158
|
+
if (!context) throw new Error('TabPanel must be used within Tabs');
|
|
159
|
+
|
|
160
|
+
return context.activeTab === id ? <div>{children}</div> : null;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Usage
|
|
164
|
+
<Tabs defaultTab="profile">
|
|
165
|
+
<TabList>
|
|
166
|
+
<Tab id="profile">Profile</Tab>
|
|
167
|
+
<Tab id="settings">Settings</Tab>
|
|
168
|
+
</TabList>
|
|
169
|
+
<TabPanel id="profile"><ProfileContent /></TabPanel>
|
|
170
|
+
<TabPanel id="settings"><SettingsContent /></TabPanel>
|
|
171
|
+
</Tabs>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Render Props Pattern
|
|
175
|
+
|
|
176
|
+
Share code between components using a prop whose value is a function.
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
interface MouseTrackerProps {
|
|
180
|
+
render: (position: { x: number; y: number }) => React.ReactNode;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const MouseTracker: React.FC<MouseTrackerProps> = ({ render }) => {
|
|
184
|
+
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
185
|
+
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
188
|
+
setPosition({ x: e.clientX, y: e.clientY });
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
window.addEventListener('mousemove', handleMouseMove);
|
|
192
|
+
return () => window.removeEventListener('mousemove', handleMouseMove);
|
|
193
|
+
}, []);
|
|
194
|
+
|
|
195
|
+
return <>{render(position)}</>;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Usage
|
|
199
|
+
<MouseTracker render={({ x, y }) => (
|
|
200
|
+
<div>Mouse position: {x}, {y}</div>
|
|
201
|
+
)} />
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Best Practices
|
|
205
|
+
|
|
206
|
+
1. **Use functional components** - Always prefer functions over classes
|
|
207
|
+
2. **Keep components small** - Single responsibility principle
|
|
208
|
+
3. **Use composition** - Build complex UIs from simple components
|
|
209
|
+
4. **Separate concerns** - Container/Presenter pattern
|
|
210
|
+
5. **Type everything** - Use TypeScript for all props
|
|
211
|
+
6. **Avoid prop drilling** - Use Context or state management
|
|
212
|
+
7. **Memoize expensive renders** - Use React.memo when needed
|
|
213
|
+
8. **Extract custom hooks** - Reuse stateful logic
|
|
214
|
+
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# React Hooks Best Practices
|
|
2
|
+
|
|
3
|
+
Guidelines for using React Hooks effectively.
|
|
4
|
+
|
|
5
|
+
## useState
|
|
6
|
+
|
|
7
|
+
Manage component state with useState.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Good - Descriptive names, proper typing
|
|
11
|
+
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
12
|
+
const [users, setUsers] = useState<User[]>([]);
|
|
13
|
+
const [error, setError] = useState<Error | null>(null);
|
|
14
|
+
|
|
15
|
+
// Good - Functional updates for state based on previous state
|
|
16
|
+
const [count, setCount] = useState(0);
|
|
17
|
+
const increment = () => setCount(prev => prev + 1);
|
|
18
|
+
|
|
19
|
+
// Bad - Non-descriptive names
|
|
20
|
+
const [data, setData] = useState([]);
|
|
21
|
+
const [flag, setFlag] = useState(false);
|
|
22
|
+
|
|
23
|
+
// Bad - Direct state mutation
|
|
24
|
+
const [items, setItems] = useState([1, 2, 3]);
|
|
25
|
+
items.push(4); // Don't mutate state directly
|
|
26
|
+
setItems(items); // This won't trigger re-render
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## useEffect
|
|
30
|
+
|
|
31
|
+
Handle side effects properly.
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
// Good - Cleanup function
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const subscription = api.subscribe(userId);
|
|
37
|
+
|
|
38
|
+
return () => {
|
|
39
|
+
subscription.unsubscribe();
|
|
40
|
+
};
|
|
41
|
+
}, [userId]);
|
|
42
|
+
|
|
43
|
+
// Good - Separate effects for different concerns
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
fetchUserData(userId);
|
|
46
|
+
}, [userId]);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
trackPageView(pageName);
|
|
50
|
+
}, [pageName]);
|
|
51
|
+
|
|
52
|
+
// Bad - Missing dependencies
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
fetchData(userId); // userId should be in deps
|
|
55
|
+
}, []); // Missing dependency
|
|
56
|
+
|
|
57
|
+
// Bad - Multiple unrelated effects
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
fetchUserData(userId);
|
|
60
|
+
trackPageView(pageName);
|
|
61
|
+
updateTitle(title);
|
|
62
|
+
}, [userId, pageName, title]);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## useCallback
|
|
66
|
+
|
|
67
|
+
Memoize callback functions.
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
// Good - Memoize callbacks passed to child components
|
|
71
|
+
const handleSubmit = useCallback((data: FormData) => {
|
|
72
|
+
submitForm(data);
|
|
73
|
+
}, [submitForm]);
|
|
74
|
+
|
|
75
|
+
// Good - Memoize event handlers with dependencies
|
|
76
|
+
const handleUserClick = useCallback((userId: string) => {
|
|
77
|
+
navigate(`/users/${userId}`);
|
|
78
|
+
}, [navigate]);
|
|
79
|
+
|
|
80
|
+
// Bad - Creating new function on every render
|
|
81
|
+
const handleClick = () => {
|
|
82
|
+
doSomething();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Bad - Unnecessary useCallback
|
|
86
|
+
const handleClick = useCallback(() => {
|
|
87
|
+
console.log('clicked');
|
|
88
|
+
}, []); // Not needed if not passed to memoized child
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## useMemo
|
|
92
|
+
|
|
93
|
+
Memoize expensive computations.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
// Good - Memoize expensive calculations
|
|
97
|
+
const sortedUsers = useMemo(() => {
|
|
98
|
+
return users.sort((a, b) => a.name.localeCompare(b.name));
|
|
99
|
+
}, [users]);
|
|
100
|
+
|
|
101
|
+
// Good - Memoize complex objects
|
|
102
|
+
const config = useMemo(() => ({
|
|
103
|
+
apiUrl: process.env.API_URL,
|
|
104
|
+
timeout: 5000,
|
|
105
|
+
headers: { 'Content-Type': 'application/json' }
|
|
106
|
+
}), []);
|
|
107
|
+
|
|
108
|
+
// Bad - Unnecessary memoization
|
|
109
|
+
const fullName = useMemo(() => `${firstName} ${lastName}`, [firstName, lastName]);
|
|
110
|
+
// Simple string concatenation doesn't need memoization
|
|
111
|
+
|
|
112
|
+
// Bad - Memoizing everything
|
|
113
|
+
const value = useMemo(() => x + y, [x, y]); // Too simple
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## useRef
|
|
117
|
+
|
|
118
|
+
Access DOM elements and persist values.
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
// Good - DOM reference
|
|
122
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
123
|
+
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
inputRef.current?.focus();
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
return <input ref={inputRef} />;
|
|
129
|
+
|
|
130
|
+
// Good - Persist value across renders
|
|
131
|
+
const previousValue = useRef<string>();
|
|
132
|
+
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
previousValue.current = value;
|
|
135
|
+
}, [value]);
|
|
136
|
+
|
|
137
|
+
// Bad - Using ref for state
|
|
138
|
+
const countRef = useRef(0);
|
|
139
|
+
countRef.current++; // Use useState instead
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## useContext
|
|
143
|
+
|
|
144
|
+
Consume context values.
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
// Good - Type-safe context
|
|
148
|
+
interface ThemeContextValue {
|
|
149
|
+
theme: 'light' | 'dark';
|
|
150
|
+
toggleTheme: () => void;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
|
154
|
+
|
|
155
|
+
export const useTheme = () => {
|
|
156
|
+
const context = useContext(ThemeContext);
|
|
157
|
+
if (!context) {
|
|
158
|
+
throw new Error('useTheme must be used within ThemeProvider');
|
|
159
|
+
}
|
|
160
|
+
return context;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Usage
|
|
164
|
+
const { theme, toggleTheme } = useTheme();
|
|
165
|
+
|
|
166
|
+
// Bad - No error handling
|
|
167
|
+
const theme = useContext(ThemeContext); // Could be undefined
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Custom Hooks
|
|
171
|
+
|
|
172
|
+
Extract reusable logic into custom hooks.
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
// Good - Custom hook for data fetching
|
|
176
|
+
function useUser(userId: string) {
|
|
177
|
+
const [user, setUser] = useState<User | null>(null);
|
|
178
|
+
const [loading, setLoading] = useState(true);
|
|
179
|
+
const [error, setError] = useState<Error | null>(null);
|
|
180
|
+
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
let cancelled = false;
|
|
183
|
+
|
|
184
|
+
const fetchUser = async () => {
|
|
185
|
+
try {
|
|
186
|
+
setLoading(true);
|
|
187
|
+
const data = await api.getUser(userId);
|
|
188
|
+
if (!cancelled) {
|
|
189
|
+
setUser(data);
|
|
190
|
+
setError(null);
|
|
191
|
+
}
|
|
192
|
+
} catch (err) {
|
|
193
|
+
if (!cancelled) {
|
|
194
|
+
setError(err as Error);
|
|
195
|
+
}
|
|
196
|
+
} finally {
|
|
197
|
+
if (!cancelled) {
|
|
198
|
+
setLoading(false);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
fetchUser();
|
|
204
|
+
|
|
205
|
+
return () => {
|
|
206
|
+
cancelled = true;
|
|
207
|
+
};
|
|
208
|
+
}, [userId]);
|
|
209
|
+
|
|
210
|
+
return { user, loading, error };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Usage
|
|
214
|
+
const { user, loading, error } = useUser(userId);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Rules of Hooks
|
|
218
|
+
|
|
219
|
+
1. **Only call hooks at the top level** - Don't call inside loops, conditions, or nested functions
|
|
220
|
+
2. **Only call hooks from React functions** - Components or custom hooks
|
|
221
|
+
3. **Name custom hooks with "use" prefix** - useCustomHook
|
|
222
|
+
4. **Include all dependencies** - In useEffect, useCallback, useMemo
|
|
223
|
+
5. **Clean up effects** - Return cleanup function when needed
|
|
224
|
+
|
|
225
|
+
## Best Practices
|
|
226
|
+
|
|
227
|
+
1. **Use ESLint plugin** - eslint-plugin-react-hooks
|
|
228
|
+
2. **Keep effects focused** - One effect per concern
|
|
229
|
+
3. **Memoize callbacks** - When passing to memoized children
|
|
230
|
+
4. **Extract custom hooks** - For reusable logic
|
|
231
|
+
5. **Type everything** - Use TypeScript for all hooks
|
|
232
|
+
6. **Handle cleanup** - Always clean up subscriptions/timers
|
|
233
|
+
7. **Avoid unnecessary memoization** - Profile before optimizing
|
|
234
|
+
8. **Use functional updates** - When new state depends on old state
|
|
235
|
+
|