start-vibing-stacks 2.0.2 → 2.0.3
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/dist/ui.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Start Vibing Stacks — Terminal UI
|
|
3
3
|
*/
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
const VERSION = '2.0.
|
|
5
|
+
const VERSION = '2.0.3';
|
|
6
6
|
const gradient = (text) => {
|
|
7
7
|
const colors = [chalk.hex('#FF6B6B'), chalk.hex('#FF8E53'), chalk.hex('#FFBD2E'), chalk.hex('#48BB78'), chalk.hex('#4299E1'), chalk.hex('#9F7AEA')];
|
|
8
8
|
return text.split('').map((c, i) => colors[i % colors.length](c)).join('');
|
package/package.json
CHANGED
|
@@ -104,10 +104,121 @@ const Heavy = lazy(() => import('./HeavyComponent'));
|
|
|
104
104
|
<Suspense fallback={<Loading />}><Heavy /></Suspense>
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
+
## React 19 Hooks
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
// useActionState — form submission state
|
|
111
|
+
import { useActionState } from 'react';
|
|
112
|
+
|
|
113
|
+
function LoginForm() {
|
|
114
|
+
const [state, formAction, isPending] = useActionState(
|
|
115
|
+
async (prev, formData: FormData) => {
|
|
116
|
+
const result = await login(formData);
|
|
117
|
+
if (result.error) return { error: result.error };
|
|
118
|
+
redirect('/dashboard');
|
|
119
|
+
},
|
|
120
|
+
{ error: null }
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<form action={formAction}>
|
|
125
|
+
<input name="email" type="email" />
|
|
126
|
+
{state.error && <p className="text-destructive">{state.error}</p>}
|
|
127
|
+
<button disabled={isPending}>
|
|
128
|
+
{isPending ? 'Signing in...' : 'Sign In'}
|
|
129
|
+
</button>
|
|
130
|
+
</form>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// useOptimistic — instant UI feedback
|
|
135
|
+
import { useOptimistic } from 'react';
|
|
136
|
+
|
|
137
|
+
function TodoList({ todos }: { todos: Todo[] }) {
|
|
138
|
+
const [optimisticTodos, addOptimistic] = useOptimistic(
|
|
139
|
+
todos,
|
|
140
|
+
(state, newTodo: Todo) => [...state, newTodo]
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const addTodo = async (formData: FormData) => {
|
|
144
|
+
const todo = { id: crypto.randomUUID(), title: formData.get('title') as string, done: false };
|
|
145
|
+
addOptimistic(todo); // Instant UI
|
|
146
|
+
await saveTodo(todo); // Server sync
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
return <ul>{optimisticTodos.map(t => <li key={t.id}>{t.title}</li>)}</ul>;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## State Management Selection
|
|
154
|
+
|
|
155
|
+
| Complexity | Solution | When |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| Simple local | `useState` | Single component, simple values |
|
|
158
|
+
| Complex local | `useReducer` | Multiple related state transitions |
|
|
159
|
+
| Parent-child | Lift state up | 1-2 levels |
|
|
160
|
+
| Subtree | Context + `useReducer` | Theme, auth, 3+ levels |
|
|
161
|
+
| Server state | React Query / SWR | API data, caching, refetch |
|
|
162
|
+
| Complex global | Zustand | Cross-feature, many consumers |
|
|
163
|
+
|
|
164
|
+
## Error Boundaries
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import { Component, type ReactNode } from 'react';
|
|
168
|
+
|
|
169
|
+
class ErrorBoundary extends Component<
|
|
170
|
+
{ children: ReactNode; fallback?: ReactNode },
|
|
171
|
+
{ hasError: boolean; error?: Error }
|
|
172
|
+
> {
|
|
173
|
+
state = { hasError: false, error: undefined as Error | undefined };
|
|
174
|
+
|
|
175
|
+
static getDerivedStateFromError(error: Error) {
|
|
176
|
+
return { hasError: true, error };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
|
180
|
+
console.error('ErrorBoundary caught:', error, info);
|
|
181
|
+
// Send to Sentry/logging
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
render() {
|
|
185
|
+
if (this.state.hasError) {
|
|
186
|
+
return this.props.fallback ?? (
|
|
187
|
+
<div className="p-8 text-center">
|
|
188
|
+
<h2 className="text-lg font-semibold text-foreground">Something went wrong</h2>
|
|
189
|
+
<button onClick={() => this.setState({ hasError: false })}
|
|
190
|
+
className="mt-4 px-4 py-2 bg-primary text-primary-foreground rounded-lg">
|
|
191
|
+
Try Again
|
|
192
|
+
</button>
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
return this.props.children;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Usage: wrap routes/features
|
|
201
|
+
<ErrorBoundary fallback={<ErrorPage />}>
|
|
202
|
+
<DashboardFeature />
|
|
203
|
+
</ErrorBoundary>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Component Types
|
|
207
|
+
|
|
208
|
+
| Type | Use | Example |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| **Server** (RSC) | Data fetching, static content | Page-level components |
|
|
211
|
+
| **Client** (`'use client'`) | Interactivity, hooks, browser APIs | Forms, modals, dropdowns |
|
|
212
|
+
| **Presentational** | Pure display, props only | `<Badge>`, `<Avatar>` |
|
|
213
|
+
| **Container** | Logic + state, renders presentational | `<UserListContainer>` |
|
|
214
|
+
|
|
107
215
|
## FORBIDDEN
|
|
108
216
|
|
|
109
|
-
1. **Class components** — function components only
|
|
217
|
+
1. **Class components** — function components only (except ErrorBoundary)
|
|
110
218
|
2. **Prop drilling** — use context or composition
|
|
111
219
|
3. **Inline objects/functions in JSX** — causes re-renders
|
|
112
220
|
4. **useEffect for derived state** — use useMemo
|
|
113
221
|
5. **Mutating state directly** — always setState/dispatch
|
|
222
|
+
6. **Index as key** — use stable unique ID (`uuid`, `id`)
|
|
223
|
+
7. **Premature optimization** — profile first with React DevTools
|
|
224
|
+
8. **useEffect for everything** — prefer server components for data
|