@stackguide/mcp-server 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/LICENSE +674 -0
- package/README.md +453 -0
- package/data/knowledge/python-django/architecture/architecture-patterns.md +201 -0
- package/data/knowledge/python-django/common-issues/common-issues.md +181 -0
- package/data/knowledge/python-django/patterns/drf-patterns.md +133 -0
- package/data/knowledge/react-node/architecture/node-architecture.md +257 -0
- package/data/knowledge/react-node/common-issues/common-issues.md +262 -0
- package/data/knowledge/react-node/patterns/react-patterns.md +244 -0
- package/data/rules/python-django/best-practices/django-best-practices.md +120 -0
- package/data/rules/python-django/coding-standards/django-standards.md +104 -0
- package/data/rules/python-django/security/security-guidelines.md +146 -0
- package/data/rules/react-node/best-practices/react-best-practices.md +195 -0
- package/data/rules/react-node/coding-standards/node-standards.md +192 -0
- package/data/rules/react-node/coding-standards/react-standards.md +155 -0
- package/data/rules/react-node/security/security-guidelines.md +228 -0
- package/dist/config/persistence.d.ts +15 -0
- package/dist/config/persistence.d.ts.map +1 -0
- package/dist/config/persistence.js +171 -0
- package/dist/config/persistence.js.map +1 -0
- package/dist/config/types.d.ts +47 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +116 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1799 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/knowledgeProvider.d.ts +10 -0
- package/dist/resources/knowledgeProvider.d.ts.map +1 -0
- package/dist/resources/knowledgeProvider.js +130 -0
- package/dist/resources/knowledgeProvider.js.map +1 -0
- package/dist/resources/rulesProvider.d.ts +10 -0
- package/dist/resources/rulesProvider.d.ts.map +1 -0
- package/dist/resources/rulesProvider.js +135 -0
- package/dist/resources/rulesProvider.js.map +1 -0
- package/dist/services/cursorDirectory.d.ts +55 -0
- package/dist/services/cursorDirectory.d.ts.map +1 -0
- package/dist/services/cursorDirectory.js +367 -0
- package/dist/services/cursorDirectory.js.map +1 -0
- package/dist/services/ruleManager.d.ts +18 -0
- package/dist/services/ruleManager.d.ts.map +1 -0
- package/dist/services/ruleManager.js +382 -0
- package/dist/services/ruleManager.js.map +1 -0
- package/dist/services/webDocumentation.d.ts +41 -0
- package/dist/services/webDocumentation.d.ts.map +1 -0
- package/dist/services/webDocumentation.js +237 -0
- package/dist/services/webDocumentation.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# React/Node Common Issues and Solutions
|
|
2
|
+
|
|
3
|
+
Solutions to frequently encountered problems in React and Node.js development.
|
|
4
|
+
|
|
5
|
+
## React Issues
|
|
6
|
+
|
|
7
|
+
### State Not Updating
|
|
8
|
+
|
|
9
|
+
**Problem**: State doesn't update as expected.
|
|
10
|
+
|
|
11
|
+
**Solution**:
|
|
12
|
+
```tsx
|
|
13
|
+
// State updates are asynchronous and batched
|
|
14
|
+
const [count, setCount] = useState(0);
|
|
15
|
+
|
|
16
|
+
// Wrong - uses stale value
|
|
17
|
+
setCount(count + 1);
|
|
18
|
+
setCount(count + 1); // Still increments by 1
|
|
19
|
+
|
|
20
|
+
// Correct - use functional update
|
|
21
|
+
setCount(prev => prev + 1);
|
|
22
|
+
setCount(prev => prev + 1); // Increments by 2
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### useEffect Running Twice
|
|
26
|
+
|
|
27
|
+
**Problem**: Effect runs twice in development.
|
|
28
|
+
|
|
29
|
+
**Solution**:
|
|
30
|
+
```tsx
|
|
31
|
+
// React 18+ Strict Mode intentionally double-invokes effects
|
|
32
|
+
// to help find bugs. Don't disable Strict Mode - fix the code.
|
|
33
|
+
|
|
34
|
+
// Ensure cleanup handles this
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
|
|
38
|
+
fetchData({ signal: controller.signal });
|
|
39
|
+
|
|
40
|
+
return () => controller.abort(); // Cleanup
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
// For subscriptions
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const unsubscribe = subscribe(handler);
|
|
46
|
+
return () => unsubscribe();
|
|
47
|
+
}, []);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Memory Leaks in Components
|
|
51
|
+
|
|
52
|
+
**Problem**: "Can't perform a React state update on an unmounted component"
|
|
53
|
+
|
|
54
|
+
**Solution**:
|
|
55
|
+
```tsx
|
|
56
|
+
// Use AbortController for fetch
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const controller = new AbortController();
|
|
59
|
+
|
|
60
|
+
async function fetchData() {
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
63
|
+
const data = await res.json();
|
|
64
|
+
setData(data);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err.name !== 'AbortError') {
|
|
67
|
+
setError(err);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fetchData();
|
|
73
|
+
return () => controller.abort();
|
|
74
|
+
}, [url]);
|
|
75
|
+
|
|
76
|
+
// Or use ref to track mount state (less preferred)
|
|
77
|
+
const isMounted = useRef(true);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
return () => { isMounted.current = false; };
|
|
80
|
+
}, []);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Prop Drilling
|
|
84
|
+
|
|
85
|
+
**Problem**: Passing props through many levels.
|
|
86
|
+
|
|
87
|
+
**Solution**:
|
|
88
|
+
```tsx
|
|
89
|
+
// Use Context for truly global state
|
|
90
|
+
const ThemeContext = createContext<Theme>('light');
|
|
91
|
+
|
|
92
|
+
function App() {
|
|
93
|
+
const [theme, setTheme] = useState<Theme>('light');
|
|
94
|
+
return (
|
|
95
|
+
<ThemeContext.Provider value={theme}>
|
|
96
|
+
<Page />
|
|
97
|
+
</ThemeContext.Provider>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Any nested component can access
|
|
102
|
+
function DeepComponent() {
|
|
103
|
+
const theme = useContext(ThemeContext);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Or use composition
|
|
107
|
+
function Page() {
|
|
108
|
+
const user = useUser();
|
|
109
|
+
return <Layout header={<Header user={user} />} />;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Node.js Issues
|
|
114
|
+
|
|
115
|
+
### Callback Hell
|
|
116
|
+
|
|
117
|
+
**Problem**: Deeply nested callbacks.
|
|
118
|
+
|
|
119
|
+
**Solution**:
|
|
120
|
+
```typescript
|
|
121
|
+
// Convert to promises
|
|
122
|
+
import { promisify } from 'util';
|
|
123
|
+
const readFile = promisify(fs.readFile);
|
|
124
|
+
|
|
125
|
+
// Use async/await
|
|
126
|
+
async function processFile(path: string) {
|
|
127
|
+
const content = await readFile(path, 'utf-8');
|
|
128
|
+
const parsed = JSON.parse(content);
|
|
129
|
+
return transform(parsed);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Handle errors properly
|
|
133
|
+
try {
|
|
134
|
+
const result = await processFile('./data.json');
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logger.error('Failed to process file', { error });
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Unhandled Promise Rejections
|
|
141
|
+
|
|
142
|
+
**Problem**: Crash due to unhandled rejection.
|
|
143
|
+
|
|
144
|
+
**Solution**:
|
|
145
|
+
```typescript
|
|
146
|
+
// Global handler
|
|
147
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
148
|
+
logger.error('Unhandled Rejection', { reason, promise });
|
|
149
|
+
// Optionally exit
|
|
150
|
+
process.exit(1);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Always handle promises
|
|
154
|
+
async function riskyOperation() {
|
|
155
|
+
try {
|
|
156
|
+
await mightFail();
|
|
157
|
+
} catch (error) {
|
|
158
|
+
handleError(error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Or with .catch()
|
|
163
|
+
mightFail().catch(handleError);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Event Loop Blocking
|
|
167
|
+
|
|
168
|
+
**Problem**: Long-running sync operation blocks the server.
|
|
169
|
+
|
|
170
|
+
**Solution**:
|
|
171
|
+
```typescript
|
|
172
|
+
// Bad - blocks event loop
|
|
173
|
+
app.get('/hash', (req, res) => {
|
|
174
|
+
const hash = bcrypt.hashSync(req.body.password, 12); // Blocks!
|
|
175
|
+
res.json({ hash });
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Good - use async version
|
|
179
|
+
app.get('/hash', async (req, res) => {
|
|
180
|
+
const hash = await bcrypt.hash(req.body.password, 12);
|
|
181
|
+
res.json({ hash });
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// For CPU-intensive work, use worker threads
|
|
185
|
+
import { Worker } from 'worker_threads';
|
|
186
|
+
|
|
187
|
+
function runHeavyTask(data) {
|
|
188
|
+
return new Promise((resolve, reject) => {
|
|
189
|
+
const worker = new Worker('./worker.js', { workerData: data });
|
|
190
|
+
worker.on('message', resolve);
|
|
191
|
+
worker.on('error', reject);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Memory Leaks
|
|
197
|
+
|
|
198
|
+
**Problem**: Node.js process memory keeps growing.
|
|
199
|
+
|
|
200
|
+
**Solution**:
|
|
201
|
+
```typescript
|
|
202
|
+
// Check for common causes:
|
|
203
|
+
|
|
204
|
+
// 1. Growing arrays/maps without cleanup
|
|
205
|
+
const cache = new Map();
|
|
206
|
+
// Add max size limit
|
|
207
|
+
function set(key: string, value: any) {
|
|
208
|
+
if (cache.size >= MAX_SIZE) {
|
|
209
|
+
const firstKey = cache.keys().next().value;
|
|
210
|
+
cache.delete(firstKey);
|
|
211
|
+
}
|
|
212
|
+
cache.set(key, value);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 2. Event listeners not removed
|
|
216
|
+
const handler = () => {};
|
|
217
|
+
emitter.on('event', handler);
|
|
218
|
+
// Later...
|
|
219
|
+
emitter.off('event', handler);
|
|
220
|
+
|
|
221
|
+
// 3. Closures holding references
|
|
222
|
+
function createHandler(bigData) {
|
|
223
|
+
// bigData is retained in closure
|
|
224
|
+
return () => console.log(bigData.length);
|
|
225
|
+
}
|
|
226
|
+
// Solution: extract only needed data
|
|
227
|
+
function createHandler(dataLength) {
|
|
228
|
+
return () => console.log(dataLength);
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Database Connection Issues
|
|
233
|
+
|
|
234
|
+
**Problem**: Too many connections or connection timeouts.
|
|
235
|
+
|
|
236
|
+
**Solution**:
|
|
237
|
+
```typescript
|
|
238
|
+
// Use connection pooling
|
|
239
|
+
import { Pool } from 'pg';
|
|
240
|
+
|
|
241
|
+
const pool = new Pool({
|
|
242
|
+
max: 20,
|
|
243
|
+
idleTimeoutMillis: 30000,
|
|
244
|
+
connectionTimeoutMillis: 2000,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Use single client for transactions
|
|
248
|
+
async function transfer(from: string, to: string, amount: number) {
|
|
249
|
+
const client = await pool.connect();
|
|
250
|
+
try {
|
|
251
|
+
await client.query('BEGIN');
|
|
252
|
+
await client.query('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [amount, from]);
|
|
253
|
+
await client.query('UPDATE accounts SET balance = balance + $1 WHERE id = $2', [amount, to]);
|
|
254
|
+
await client.query('COMMIT');
|
|
255
|
+
} catch (e) {
|
|
256
|
+
await client.query('ROLLBACK');
|
|
257
|
+
throw e;
|
|
258
|
+
} finally {
|
|
259
|
+
client.release(); // Always release!
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# React Component Patterns
|
|
2
|
+
|
|
3
|
+
Common patterns for building scalable React components.
|
|
4
|
+
|
|
5
|
+
## Compound Components
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { createContext, useContext, useState, ReactNode } from 'react';
|
|
9
|
+
|
|
10
|
+
// Context for compound component
|
|
11
|
+
const TabsContext = createContext<{
|
|
12
|
+
activeTab: string;
|
|
13
|
+
setActiveTab: (id: string) => void;
|
|
14
|
+
} | null>(null);
|
|
15
|
+
|
|
16
|
+
// Hook to access context
|
|
17
|
+
const useTabsContext = () => {
|
|
18
|
+
const context = useContext(TabsContext);
|
|
19
|
+
if (!context) throw new Error('Tab components must be used within Tabs');
|
|
20
|
+
return context;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Main component
|
|
24
|
+
interface TabsProps {
|
|
25
|
+
defaultTab: string;
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function Tabs({ defaultTab, children }: TabsProps) {
|
|
30
|
+
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
|
|
34
|
+
<div className="tabs">{children}</div>
|
|
35
|
+
</TabsContext.Provider>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Sub-components
|
|
40
|
+
function TabList({ children }: { children: ReactNode }) {
|
|
41
|
+
return <div className="tab-list" role="tablist">{children}</div>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function Tab({ id, children }: { id: string; children: ReactNode }) {
|
|
45
|
+
const { activeTab, setActiveTab } = useTabsContext();
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<button
|
|
49
|
+
role="tab"
|
|
50
|
+
aria-selected={activeTab === id}
|
|
51
|
+
onClick={() => setActiveTab(id)}
|
|
52
|
+
className={activeTab === id ? 'active' : ''}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
</button>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function TabPanel({ id, children }: { id: string; children: ReactNode }) {
|
|
60
|
+
const { activeTab } = useTabsContext();
|
|
61
|
+
if (activeTab !== id) return null;
|
|
62
|
+
return <div role="tabpanel">{children}</div>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Attach sub-components
|
|
66
|
+
Tabs.List = TabList;
|
|
67
|
+
Tabs.Tab = Tab;
|
|
68
|
+
Tabs.Panel = TabPanel;
|
|
69
|
+
|
|
70
|
+
// Usage
|
|
71
|
+
<Tabs defaultTab="overview">
|
|
72
|
+
<Tabs.List>
|
|
73
|
+
<Tabs.Tab id="overview">Overview</Tabs.Tab>
|
|
74
|
+
<Tabs.Tab id="details">Details</Tabs.Tab>
|
|
75
|
+
</Tabs.List>
|
|
76
|
+
<Tabs.Panel id="overview">Overview content</Tabs.Panel>
|
|
77
|
+
<Tabs.Panel id="details">Details content</Tabs.Panel>
|
|
78
|
+
</Tabs>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Render Props
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
interface MousePosition {
|
|
85
|
+
x: number;
|
|
86
|
+
y: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface MouseTrackerProps {
|
|
90
|
+
render: (position: MousePosition) => ReactNode;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function MouseTracker({ render }: MouseTrackerProps) {
|
|
94
|
+
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
const handleMove = (e: MouseEvent) => {
|
|
98
|
+
setPosition({ x: e.clientX, y: e.clientY });
|
|
99
|
+
};
|
|
100
|
+
window.addEventListener('mousemove', handleMove);
|
|
101
|
+
return () => window.removeEventListener('mousemove', handleMove);
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
return <>{render(position)}</>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Usage
|
|
108
|
+
<MouseTracker
|
|
109
|
+
render={({ x, y }) => (
|
|
110
|
+
<div>Mouse is at ({x}, {y})</div>
|
|
111
|
+
)}
|
|
112
|
+
/>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Higher-Order Components (HOC)
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
interface WithLoadingProps {
|
|
119
|
+
isLoading: boolean;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function withLoading<P extends object>(
|
|
123
|
+
WrappedComponent: React.ComponentType<P>
|
|
124
|
+
) {
|
|
125
|
+
return function WithLoadingComponent({
|
|
126
|
+
isLoading,
|
|
127
|
+
...props
|
|
128
|
+
}: P & WithLoadingProps) {
|
|
129
|
+
if (isLoading) return <LoadingSpinner />;
|
|
130
|
+
return <WrappedComponent {...(props as P)} />;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Usage
|
|
135
|
+
const UserListWithLoading = withLoading(UserList);
|
|
136
|
+
<UserListWithLoading isLoading={loading} users={users} />
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Custom Hooks for Shared Logic
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
// useAsync hook for data fetching
|
|
143
|
+
function useAsync<T>(asyncFn: () => Promise<T>, deps: any[] = []) {
|
|
144
|
+
const [state, setState] = useState<{
|
|
145
|
+
status: 'idle' | 'pending' | 'success' | 'error';
|
|
146
|
+
data?: T;
|
|
147
|
+
error?: Error;
|
|
148
|
+
}>({ status: 'idle' });
|
|
149
|
+
|
|
150
|
+
const execute = useCallback(async () => {
|
|
151
|
+
setState({ status: 'pending' });
|
|
152
|
+
try {
|
|
153
|
+
const data = await asyncFn();
|
|
154
|
+
setState({ status: 'success', data });
|
|
155
|
+
} catch (error) {
|
|
156
|
+
setState({ status: 'error', error: error as Error });
|
|
157
|
+
}
|
|
158
|
+
}, deps);
|
|
159
|
+
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
execute();
|
|
162
|
+
}, [execute]);
|
|
163
|
+
|
|
164
|
+
return { ...state, execute };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Usage
|
|
168
|
+
const { data, status, error } = useAsync(
|
|
169
|
+
() => fetchUsers(),
|
|
170
|
+
[]
|
|
171
|
+
);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Controlled vs Uncontrolled
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
interface InputProps {
|
|
178
|
+
// Controlled
|
|
179
|
+
value?: string;
|
|
180
|
+
onChange?: (value: string) => void;
|
|
181
|
+
// Uncontrolled
|
|
182
|
+
defaultValue?: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function Input({ value, onChange, defaultValue }: InputProps) {
|
|
186
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? '');
|
|
187
|
+
|
|
188
|
+
const isControlled = value !== undefined;
|
|
189
|
+
const currentValue = isControlled ? value : internalValue;
|
|
190
|
+
|
|
191
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
192
|
+
const newValue = e.target.value;
|
|
193
|
+
if (!isControlled) {
|
|
194
|
+
setInternalValue(newValue);
|
|
195
|
+
}
|
|
196
|
+
onChange?.(newValue);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return <input value={currentValue} onChange={handleChange} />;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Polymorphic Components
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
type AsProp<C extends React.ElementType> = {
|
|
207
|
+
as?: C;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
type PropsToOmit<C extends React.ElementType, P> = keyof (AsProp<C> & P);
|
|
211
|
+
|
|
212
|
+
type PolymorphicComponentProp<
|
|
213
|
+
C extends React.ElementType,
|
|
214
|
+
Props = {}
|
|
215
|
+
> = React.PropsWithChildren<Props & AsProp<C>> &
|
|
216
|
+
Omit<React.ComponentPropsWithoutRef<C>, PropsToOmit<C, Props>>;
|
|
217
|
+
|
|
218
|
+
interface TextOwnProps {
|
|
219
|
+
color?: 'primary' | 'secondary';
|
|
220
|
+
size?: 'sm' | 'md' | 'lg';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
type TextProps<C extends React.ElementType> = PolymorphicComponentProp<C, TextOwnProps>;
|
|
224
|
+
|
|
225
|
+
function Text<C extends React.ElementType = 'span'>({
|
|
226
|
+
as,
|
|
227
|
+
color = 'primary',
|
|
228
|
+
size = 'md',
|
|
229
|
+
children,
|
|
230
|
+
...props
|
|
231
|
+
}: TextProps<C>) {
|
|
232
|
+
const Component = as || 'span';
|
|
233
|
+
return (
|
|
234
|
+
<Component className={`text-${color} text-${size}`} {...props}>
|
|
235
|
+
{children}
|
|
236
|
+
</Component>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Usage
|
|
241
|
+
<Text>Default span</Text>
|
|
242
|
+
<Text as="h1" size="lg">Heading</Text>
|
|
243
|
+
<Text as="a" href="/link">Link text</Text>
|
|
244
|
+
```
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Django Best Practices
|
|
2
|
+
|
|
3
|
+
Essential best practices for building robust Django applications.
|
|
4
|
+
|
|
5
|
+
## Settings Management
|
|
6
|
+
|
|
7
|
+
Use environment variables and split settings:
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
# config/settings/base.py
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
15
|
+
|
|
16
|
+
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")
|
|
17
|
+
DEBUG = False
|
|
18
|
+
ALLOWED_HOSTS = []
|
|
19
|
+
|
|
20
|
+
# config/settings/development.py
|
|
21
|
+
from .base import *
|
|
22
|
+
|
|
23
|
+
DEBUG = True
|
|
24
|
+
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Database Optimization
|
|
28
|
+
|
|
29
|
+
### Use select_related and prefetch_related
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
# Bad - N+1 queries
|
|
33
|
+
articles = Article.objects.all()
|
|
34
|
+
for article in articles:
|
|
35
|
+
print(article.author.name) # Each iteration queries DB
|
|
36
|
+
|
|
37
|
+
# Good - Single query with join
|
|
38
|
+
articles = Article.objects.select_related("author").all()
|
|
39
|
+
|
|
40
|
+
# For many-to-many or reverse foreign keys
|
|
41
|
+
articles = Article.objects.prefetch_related("tags", "comments").all()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Use QuerySet methods efficiently
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# Use exists() instead of count() for existence checks
|
|
48
|
+
if Article.objects.filter(slug=slug).exists():
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
# Use only() or defer() for partial model loading
|
|
52
|
+
Article.objects.only("title", "slug").all()
|
|
53
|
+
|
|
54
|
+
# Use values() or values_list() when you don't need model instances
|
|
55
|
+
Article.objects.values_list("id", "title")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Caching Strategies
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from django.core.cache import cache
|
|
62
|
+
from django.views.decorators.cache import cache_page
|
|
63
|
+
|
|
64
|
+
# View caching
|
|
65
|
+
@cache_page(60 * 15) # Cache for 15 minutes
|
|
66
|
+
def article_list(request):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
# Low-level caching
|
|
70
|
+
def get_article(slug):
|
|
71
|
+
key = f"article:{slug}"
|
|
72
|
+
article = cache.get(key)
|
|
73
|
+
if article is None:
|
|
74
|
+
article = Article.objects.get(slug=slug)
|
|
75
|
+
cache.set(key, article, timeout=3600)
|
|
76
|
+
return article
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Form and Validation
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from django import forms
|
|
83
|
+
from django.core.exceptions import ValidationError
|
|
84
|
+
|
|
85
|
+
class ArticleForm(forms.ModelForm):
|
|
86
|
+
class Meta:
|
|
87
|
+
model = Article
|
|
88
|
+
fields = ["title", "content", "tags"]
|
|
89
|
+
|
|
90
|
+
def clean_title(self):
|
|
91
|
+
title = self.cleaned_data["title"]
|
|
92
|
+
if len(title) < 5:
|
|
93
|
+
raise ValidationError("Title must be at least 5 characters.")
|
|
94
|
+
return title
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Signals - Use Sparingly
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from django.db.models.signals import post_save
|
|
101
|
+
from django.dispatch import receiver
|
|
102
|
+
|
|
103
|
+
@receiver(post_save, sender=Article)
|
|
104
|
+
def notify_subscribers(sender, instance, created, **kwargs):
|
|
105
|
+
if created:
|
|
106
|
+
# Send notification
|
|
107
|
+
pass
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Custom Managers
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
class PublishedManager(models.Manager):
|
|
114
|
+
def get_queryset(self):
|
|
115
|
+
return super().get_queryset().filter(status="published")
|
|
116
|
+
|
|
117
|
+
class Article(models.Model):
|
|
118
|
+
objects = models.Manager() # Default manager
|
|
119
|
+
published = PublishedManager() # Custom manager
|
|
120
|
+
```
|