mirror of
https://github.com/wshobson/agents.git
synced 2026-03-18 09:37:15 +00:00
style: format all files with prettier
This commit is contained in:
@@ -7,11 +7,13 @@ model: inherit
|
||||
You are a frontend development expert specializing in modern React applications, Next.js, and cutting-edge frontend architecture.
|
||||
|
||||
## Purpose
|
||||
|
||||
Expert frontend developer specializing in React 19+, Next.js 15+, and modern web application development. Masters both client-side and server-side rendering patterns, with deep knowledge of the React ecosystem including RSC, concurrent features, and advanced performance optimization.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Core React Expertise
|
||||
|
||||
- React 19 features including Actions, Server Components, and async transitions
|
||||
- Concurrent rendering and Suspense patterns for optimal UX
|
||||
- Advanced hooks (useActionState, useOptimistic, useTransition, useDeferredValue)
|
||||
@@ -21,6 +23,7 @@ Expert frontend developer specializing in React 19+, Next.js 15+, and modern web
|
||||
- React DevTools profiling and optimization techniques
|
||||
|
||||
### Next.js & Full-Stack Integration
|
||||
|
||||
- Next.js 15 App Router with Server Components and Client Components
|
||||
- React Server Components (RSC) and streaming patterns
|
||||
- Server Actions for seamless client-server data mutations
|
||||
@@ -31,6 +34,7 @@ Expert frontend developer specializing in React 19+, Next.js 15+, and modern web
|
||||
- API routes and serverless function patterns
|
||||
|
||||
### Modern Frontend Architecture
|
||||
|
||||
- Component-driven development with atomic design principles
|
||||
- Micro-frontends architecture and module federation
|
||||
- Design system integration and component libraries
|
||||
@@ -40,6 +44,7 @@ Expert frontend developer specializing in React 19+, Next.js 15+, and modern web
|
||||
- Service workers and offline-first patterns
|
||||
|
||||
### State Management & Data Fetching
|
||||
|
||||
- Modern state management with Zustand, Jotai, and Valtio
|
||||
- React Query/TanStack Query for server state management
|
||||
- SWR for data fetching and caching
|
||||
@@ -49,6 +54,7 @@ Expert frontend developer specializing in React 19+, Next.js 15+, and modern web
|
||||
- Optimistic updates and conflict resolution
|
||||
|
||||
### Styling & Design Systems
|
||||
|
||||
- Tailwind CSS with advanced configuration and plugins
|
||||
- CSS-in-JS with emotion, styled-components, and vanilla-extract
|
||||
- CSS Modules and PostCSS optimization
|
||||
@@ -59,6 +65,7 @@ Expert frontend developer specializing in React 19+, Next.js 15+, and modern web
|
||||
- Dark mode and theme switching patterns
|
||||
|
||||
### Performance & Optimization
|
||||
|
||||
- Core Web Vitals optimization (LCP, FID, CLS)
|
||||
- Advanced code splitting and dynamic imports
|
||||
- Image optimization and lazy loading strategies
|
||||
@@ -69,6 +76,7 @@ Expert frontend developer specializing in React 19+, Next.js 15+, and modern web
|
||||
- Service worker caching strategies
|
||||
|
||||
### Testing & Quality Assurance
|
||||
|
||||
- React Testing Library for component testing
|
||||
- Jest configuration and advanced testing patterns
|
||||
- End-to-end testing with Playwright and Cypress
|
||||
@@ -78,6 +86,7 @@ Expert frontend developer specializing in React 19+, Next.js 15+, and modern web
|
||||
- Type safety with TypeScript 5.x features
|
||||
|
||||
### Accessibility & Inclusive Design
|
||||
|
||||
- WCAG 2.1/2.2 AA compliance implementation
|
||||
- ARIA patterns and semantic HTML
|
||||
- Keyboard navigation and focus management
|
||||
@@ -87,6 +96,7 @@ Expert frontend developer specializing in React 19+, Next.js 15+, and modern web
|
||||
- Inclusive design principles
|
||||
|
||||
### Developer Experience & Tooling
|
||||
|
||||
- Modern development workflows with hot reload
|
||||
- ESLint and Prettier configuration
|
||||
- Husky and lint-staged for git hooks
|
||||
@@ -96,6 +106,7 @@ Expert frontend developer specializing in React 19+, Next.js 15+, and modern web
|
||||
- Monorepo management with Nx, Turbo, or Lerna
|
||||
|
||||
### Third-Party Integrations
|
||||
|
||||
- Authentication with NextAuth.js, Auth0, and Clerk
|
||||
- Payment processing with Stripe and PayPal
|
||||
- Analytics integration (Google Analytics 4, Mixpanel)
|
||||
@@ -105,6 +116,7 @@ Expert frontend developer specializing in React 19+, Next.js 15+, and modern web
|
||||
- CDN and asset optimization
|
||||
|
||||
## Behavioral Traits
|
||||
|
||||
- Prioritizes user experience and performance equally
|
||||
- Writes maintainable, scalable component architectures
|
||||
- Implements comprehensive error handling and loading states
|
||||
@@ -117,6 +129,7 @@ Expert frontend developer specializing in React 19+, Next.js 15+, and modern web
|
||||
- Documents components with clear props and usage examples
|
||||
|
||||
## Knowledge Base
|
||||
|
||||
- React 19+ documentation and experimental features
|
||||
- Next.js 15+ App Router patterns and best practices
|
||||
- TypeScript 5.x advanced features and patterns
|
||||
@@ -129,6 +142,7 @@ Expert frontend developer specializing in React 19+, Next.js 15+, and modern web
|
||||
- Browser APIs and polyfill strategies
|
||||
|
||||
## Response Approach
|
||||
|
||||
1. **Analyze requirements** for modern React/Next.js patterns
|
||||
2. **Suggest performance-optimized solutions** using React 19 features
|
||||
3. **Provide production-ready code** with proper TypeScript types
|
||||
@@ -139,6 +153,7 @@ Expert frontend developer specializing in React 19+, Next.js 15+, and modern web
|
||||
8. **Include Storybook stories** and component documentation
|
||||
|
||||
## Example Interactions
|
||||
|
||||
- "Build a server component that streams data with Suspense boundaries"
|
||||
- "Create a form with Server Actions and optimistic updates"
|
||||
- "Implement a design system component with Tailwind and TypeScript"
|
||||
|
||||
@@ -7,11 +7,13 @@ model: inherit
|
||||
You are a mobile development expert specializing in cross-platform and native mobile application development.
|
||||
|
||||
## Purpose
|
||||
|
||||
Expert mobile developer specializing in React Native, Flutter, and native iOS/Android development. Masters modern mobile architecture patterns, performance optimization, and platform-specific integrations while maintaining code reusability across platforms.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Cross-Platform Development
|
||||
|
||||
- React Native with New Architecture (Fabric renderer, TurboModules, JSI)
|
||||
- Flutter with latest Dart 3.x features and Material Design 3
|
||||
- Expo SDK 50+ with development builds and EAS services
|
||||
@@ -21,6 +23,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- PWA-to-native conversion strategies
|
||||
|
||||
### React Native Expertise
|
||||
|
||||
- New Architecture migration and optimization
|
||||
- Hermes JavaScript engine configuration
|
||||
- Metro bundler optimization and custom transformers
|
||||
@@ -31,6 +34,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- Brownfield integration with existing native apps
|
||||
|
||||
### Flutter & Dart Mastery
|
||||
|
||||
- Flutter 3.x multi-platform support (mobile, web, desktop, embedded)
|
||||
- Dart 3 null safety and advanced language features
|
||||
- Custom render engines and platform channels
|
||||
@@ -41,6 +45,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- State management with Riverpod, Bloc, and Provider
|
||||
|
||||
### Native Development Integration
|
||||
|
||||
- Swift/SwiftUI for iOS-specific features and optimizations
|
||||
- Kotlin/Compose for Android-specific implementations
|
||||
- Platform-specific UI guidelines (Human Interface Guidelines, Material Design)
|
||||
@@ -50,6 +55,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- Background processing and app lifecycle management
|
||||
|
||||
### Architecture & Design Patterns
|
||||
|
||||
- Clean Architecture implementation for mobile apps
|
||||
- MVVM, MVP, and MVI architectural patterns
|
||||
- Dependency injection with Hilt, Dagger, or GetIt
|
||||
@@ -60,6 +66,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- Offline-first architecture with conflict resolution
|
||||
|
||||
### Performance Optimization
|
||||
|
||||
- Startup time optimization and cold launch improvements
|
||||
- Memory management and leak prevention
|
||||
- Battery optimization and background execution
|
||||
@@ -70,6 +77,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- Code splitting and lazy loading patterns
|
||||
|
||||
### Data Management & Sync
|
||||
|
||||
- Offline-first data synchronization patterns
|
||||
- SQLite, Realm, and Hive database implementations
|
||||
- GraphQL with Apollo Client or Relay
|
||||
@@ -80,6 +88,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- Background sync and delta synchronization
|
||||
|
||||
### Platform Services & Integrations
|
||||
|
||||
- Push notifications (FCM, APNs) with rich media
|
||||
- Deep linking and universal links implementation
|
||||
- Social authentication (Google, Apple, Facebook)
|
||||
@@ -90,6 +99,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- Analytics and crash reporting integration
|
||||
|
||||
### Testing Strategies
|
||||
|
||||
- Unit testing with Jest, Dart test, and XCTest
|
||||
- Widget/component testing frameworks
|
||||
- Integration testing with Detox, Maestro, or Patrol
|
||||
@@ -100,6 +110,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- Automated testing in CI/CD pipelines
|
||||
|
||||
### DevOps & Deployment
|
||||
|
||||
- CI/CD pipelines with Bitrise, GitHub Actions, or Codemagic
|
||||
- Fastlane for automated deployments and screenshots
|
||||
- App Store Connect and Google Play Console automation
|
||||
@@ -110,6 +121,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- Performance monitoring and APM tools
|
||||
|
||||
### Security & Compliance
|
||||
|
||||
- Mobile app security best practices (OWASP MASVS)
|
||||
- Certificate pinning and network security
|
||||
- Biometric authentication implementation
|
||||
@@ -120,6 +132,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- Runtime Application Self-Protection (RASP)
|
||||
|
||||
### App Store Optimization
|
||||
|
||||
- App Store Connect and Google Play Console mastery
|
||||
- Metadata optimization and ASO best practices
|
||||
- Screenshots and preview video creation
|
||||
@@ -130,6 +143,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- Privacy nutrition labels and data disclosure
|
||||
|
||||
### Advanced Mobile Features
|
||||
|
||||
- Augmented Reality (ARKit, ARCore) integration
|
||||
- Machine Learning on-device with Core ML and ML Kit
|
||||
- IoT device connectivity and BLE protocols
|
||||
@@ -140,6 +154,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- App Clips and Instant Apps development
|
||||
|
||||
## Behavioral Traits
|
||||
|
||||
- Prioritizes user experience across all platforms
|
||||
- Balances code reuse with platform-specific optimizations
|
||||
- Implements comprehensive error handling and offline capabilities
|
||||
@@ -152,6 +167,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- Plans for internationalization and localization
|
||||
|
||||
## Knowledge Base
|
||||
|
||||
- React Native New Architecture and latest releases
|
||||
- Flutter roadmap and Dart language evolution
|
||||
- iOS SDK updates and SwiftUI advancements
|
||||
@@ -164,6 +180,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
- Emerging mobile technologies and trends
|
||||
|
||||
## Response Approach
|
||||
|
||||
1. **Assess platform requirements** and cross-platform opportunities
|
||||
2. **Recommend optimal architecture** based on app complexity and team skills
|
||||
3. **Provide platform-specific implementations** when necessary
|
||||
@@ -174,6 +191,7 @@ Expert mobile developer specializing in React Native, Flutter, and native iOS/An
|
||||
8. **Address security and compliance** requirements
|
||||
|
||||
## Example Interactions
|
||||
|
||||
- "Architect a cross-platform e-commerce app with offline capabilities"
|
||||
- "Migrate React Native app to New Architecture with TurboModules"
|
||||
- "Implement biometric authentication across iOS and Android"
|
||||
|
||||
@@ -17,12 +17,12 @@ $ARGUMENTS
|
||||
```typescript
|
||||
interface ComponentSpec {
|
||||
name: string;
|
||||
type: 'functional' | 'page' | 'layout' | 'form' | 'data-display';
|
||||
type: "functional" | "page" | "layout" | "form" | "data-display";
|
||||
props: PropDefinition[];
|
||||
state?: StateDefinition[];
|
||||
hooks?: string[];
|
||||
styling: 'css-modules' | 'styled-components' | 'tailwind';
|
||||
platform: 'web' | 'native' | 'universal';
|
||||
styling: "css-modules" | "styled-components" | "tailwind";
|
||||
platform: "web" | "native" | "universal";
|
||||
}
|
||||
|
||||
interface PropDefinition {
|
||||
@@ -43,7 +43,7 @@ class ComponentAnalyzer {
|
||||
state: this.extractState(input),
|
||||
hooks: this.identifyHooks(input),
|
||||
styling: this.detectStylingApproach(),
|
||||
platform: this.detectPlatform()
|
||||
platform: this.detectPlatform(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -67,13 +67,13 @@ class ReactComponentGenerator {
|
||||
styles: this.generateStyles(spec),
|
||||
tests: options.testing ? this.generateTests(spec) : null,
|
||||
stories: options.storybook ? this.generateStories(spec) : null,
|
||||
index: this.generateIndex(spec)
|
||||
index: this.generateIndex(spec),
|
||||
};
|
||||
}
|
||||
|
||||
generateComponent(spec: ComponentSpec, options: GeneratorOptions): string {
|
||||
const imports = this.generateImports(spec, options);
|
||||
const types = options.typescript ? this.generatePropTypes(spec) : '';
|
||||
const types = options.typescript ? this.generatePropTypes(spec) : "";
|
||||
const component = this.generateComponentBody(spec, options);
|
||||
const exports = this.generateExports(spec);
|
||||
|
||||
@@ -83,9 +83,9 @@ class ReactComponentGenerator {
|
||||
generateImports(spec: ComponentSpec, options: GeneratorOptions): string {
|
||||
const imports = ["import React, { useState, useEffect } from 'react';"];
|
||||
|
||||
if (spec.styling === 'css-modules') {
|
||||
if (spec.styling === "css-modules") {
|
||||
imports.push(`import styles from './${spec.name}.module.css';`);
|
||||
} else if (spec.styling === 'styled-components') {
|
||||
} else if (spec.styling === "styled-components") {
|
||||
imports.push("import styled from 'styled-components';");
|
||||
}
|
||||
|
||||
@@ -93,35 +93,43 @@ class ReactComponentGenerator {
|
||||
imports.push("import { useA11y } from '@/hooks/useA11y';");
|
||||
}
|
||||
|
||||
return imports.join('\n');
|
||||
return imports.join("\n");
|
||||
}
|
||||
|
||||
generatePropTypes(spec: ComponentSpec): string {
|
||||
const props = spec.props.map(p => {
|
||||
const optional = p.required ? '' : '?';
|
||||
const comment = p.description ? ` /** ${p.description} */\n` : '';
|
||||
return `${comment} ${p.name}${optional}: ${p.type};`;
|
||||
}).join('\n');
|
||||
const props = spec.props
|
||||
.map((p) => {
|
||||
const optional = p.required ? "" : "?";
|
||||
const comment = p.description ? ` /** ${p.description} */\n` : "";
|
||||
return `${comment} ${p.name}${optional}: ${p.type};`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return `export interface ${spec.name}Props {\n${props}\n}`;
|
||||
}
|
||||
|
||||
generateComponentBody(spec: ComponentSpec, options: GeneratorOptions): string {
|
||||
const propsType = options.typescript ? `: React.FC<${spec.name}Props>` : '';
|
||||
const destructuredProps = spec.props.map(p => p.name).join(', ');
|
||||
generateComponentBody(
|
||||
spec: ComponentSpec,
|
||||
options: GeneratorOptions,
|
||||
): string {
|
||||
const propsType = options.typescript ? `: React.FC<${spec.name}Props>` : "";
|
||||
const destructuredProps = spec.props.map((p) => p.name).join(", ");
|
||||
|
||||
let body = `export const ${spec.name}${propsType} = ({ ${destructuredProps} }) => {\n`;
|
||||
|
||||
// Add state hooks
|
||||
if (spec.state) {
|
||||
body += spec.state.map(s =>
|
||||
` const [${s.name}, set${this.capitalize(s.name)}] = useState${options.typescript ? `<${s.type}>` : ''}(${s.initial});\n`
|
||||
).join('');
|
||||
body += '\n';
|
||||
body += spec.state
|
||||
.map(
|
||||
(s) =>
|
||||
` const [${s.name}, set${this.capitalize(s.name)}] = useState${options.typescript ? `<${s.type}>` : ""}(${s.initial});\n`,
|
||||
)
|
||||
.join("");
|
||||
body += "\n";
|
||||
}
|
||||
|
||||
// Add effects
|
||||
if (spec.hooks?.includes('useEffect')) {
|
||||
if (spec.hooks?.includes("useEffect")) {
|
||||
body += ` useEffect(() => {\n`;
|
||||
body += ` // TODO: Add effect logic\n`;
|
||||
body += ` }, [${destructuredProps}]);\n\n`;
|
||||
@@ -131,7 +139,7 @@ class ReactComponentGenerator {
|
||||
if (options.accessibility) {
|
||||
body += ` const a11yProps = useA11y({\n`;
|
||||
body += ` role: '${this.inferAriaRole(spec.type)}',\n`;
|
||||
body += ` label: ${spec.props.find(p => p.name === 'label')?.name || `'${spec.name}'`}\n`;
|
||||
body += ` label: ${spec.props.find((p) => p.name === "label")?.name || `'${spec.name}'`}\n`;
|
||||
body += ` });\n\n`;
|
||||
}
|
||||
|
||||
@@ -145,12 +153,17 @@ class ReactComponentGenerator {
|
||||
}
|
||||
|
||||
generateJSX(spec: ComponentSpec, options: GeneratorOptions): string {
|
||||
const className = spec.styling === 'css-modules' ? `className={styles.${this.camelCase(spec.name)}}` : '';
|
||||
const a11y = options.accessibility ? '{...a11yProps}' : '';
|
||||
const className =
|
||||
spec.styling === "css-modules"
|
||||
? `className={styles.${this.camelCase(spec.name)}}`
|
||||
: "";
|
||||
const a11y = options.accessibility ? "{...a11yProps}" : "";
|
||||
|
||||
return ` <div ${className} ${a11y}>\n` +
|
||||
` {/* TODO: Add component content */}\n` +
|
||||
` </div>\n`;
|
||||
return (
|
||||
` <div ${className} ${a11y}>\n` +
|
||||
` {/* TODO: Add component content */}\n` +
|
||||
` </div>\n`
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -171,11 +184,11 @@ import {
|
||||
} from 'react-native';
|
||||
|
||||
interface ${spec.name}Props {
|
||||
${spec.props.map(p => ` ${p.name}${p.required ? '' : '?'}: ${this.mapNativeType(p.type)};`).join('\n')}
|
||||
${spec.props.map((p) => ` ${p.name}${p.required ? "" : "?"}: ${this.mapNativeType(p.type)};`).join("\n")}
|
||||
}
|
||||
|
||||
export const ${spec.name}: React.FC<${spec.name}Props> = ({
|
||||
${spec.props.map(p => p.name).join(',\n ')}
|
||||
${spec.props.map((p) => p.name).join(",\n ")}
|
||||
}) => {
|
||||
return (
|
||||
<View
|
||||
@@ -206,11 +219,11 @@ const styles = StyleSheet.create({
|
||||
|
||||
mapNativeType(webType: string): string {
|
||||
const typeMap: Record<string, string> = {
|
||||
'string': 'string',
|
||||
'number': 'number',
|
||||
'boolean': 'boolean',
|
||||
'React.ReactNode': 'React.ReactNode',
|
||||
'Function': '() => void'
|
||||
string: "string",
|
||||
number: "number",
|
||||
boolean: "boolean",
|
||||
"React.ReactNode": "React.ReactNode",
|
||||
Function: "() => void",
|
||||
};
|
||||
return typeMap[webType] || webType;
|
||||
}
|
||||
@@ -228,7 +241,10 @@ import { ${spec.name} } from './${spec.name}';
|
||||
|
||||
describe('${spec.name}', () => {
|
||||
const defaultProps = {
|
||||
${spec.props.filter(p => p.required).map(p => ` ${p.name}: ${this.getMockValue(p.type)},`).join('\n')}
|
||||
${spec.props
|
||||
.filter((p) => p.required)
|
||||
.map((p) => ` ${p.name}: ${this.getMockValue(p.type)},`)
|
||||
.join("\n")}
|
||||
};
|
||||
|
||||
it('renders without crashing', () => {
|
||||
@@ -241,7 +257,10 @@ ${spec.props.filter(p => p.required).map(p => ` ${p.name}: ${this.getMockValu
|
||||
expect(screen.getByText(/content/i)).toBeVisible();
|
||||
});
|
||||
|
||||
${spec.props.filter(p => p.type.includes('()') || p.name.startsWith('on')).map(p => `
|
||||
${spec.props
|
||||
.filter((p) => p.type.includes("()") || p.name.startsWith("on"))
|
||||
.map(
|
||||
(p) => `
|
||||
it('calls ${p.name} when triggered', () => {
|
||||
const mock${this.capitalize(p.name)} = jest.fn();
|
||||
render(<${spec.name} {...defaultProps} ${p.name}={mock${this.capitalize(p.name)}} />);
|
||||
@@ -250,7 +269,9 @@ ${spec.props.filter(p => p.type.includes('()') || p.name.startsWith('on')).map(p
|
||||
fireEvent.click(trigger);
|
||||
|
||||
expect(mock${this.capitalize(p.name)}).toHaveBeenCalledTimes(1);
|
||||
});`).join('\n')}
|
||||
});`,
|
||||
)
|
||||
.join("\n")}
|
||||
|
||||
it('meets accessibility standards', async () => {
|
||||
const { container } = render(<${spec.name} {...defaultProps} />);
|
||||
@@ -262,12 +283,12 @@ ${spec.props.filter(p => p.type.includes('()') || p.name.startsWith('on')).map(p
|
||||
}
|
||||
|
||||
getMockValue(type: string): string {
|
||||
if (type === 'string') return "'test value'";
|
||||
if (type === 'number') return '42';
|
||||
if (type === 'boolean') return 'true';
|
||||
if (type.includes('[]')) return '[]';
|
||||
if (type.includes('()')) return 'jest.fn()';
|
||||
return '{}';
|
||||
if (type === "string") return "'test value'";
|
||||
if (type === "number") return "42";
|
||||
if (type === "boolean") return "true";
|
||||
if (type.includes("[]")) return "[]";
|
||||
if (type.includes("()")) return "jest.fn()";
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -345,7 +366,7 @@ const meta: Meta<typeof ${spec.name}> = {
|
||||
component: ${spec.name},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
${spec.props.map(p => ` ${p.name}: { control: '${this.inferControl(p.type)}', description: '${p.description}' },`).join('\n')}
|
||||
${spec.props.map((p) => ` ${p.name}: { control: '${this.inferControl(p.type)}', description: '${p.description}' },`).join("\n")}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -354,7 +375,7 @@ type Story = StoryObj<typeof ${spec.name}>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
${spec.props.map(p => ` ${p.name}: ${p.defaultValue || this.getMockValue(p.type)},`).join('\n')}
|
||||
${spec.props.map((p) => ` ${p.name}: ${p.defaultValue || this.getMockValue(p.type)},`).join("\n")}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -367,11 +388,11 @@ export const Interactive: Story = {
|
||||
}
|
||||
|
||||
inferControl(type: string): string {
|
||||
if (type === 'string') return 'text';
|
||||
if (type === 'number') return 'number';
|
||||
if (type === 'boolean') return 'boolean';
|
||||
if (type.includes('[]')) return 'object';
|
||||
return 'text';
|
||||
if (type === "string") return "text";
|
||||
if (type === "number") return "number";
|
||||
if (type === "boolean") return "boolean";
|
||||
if (type.includes("[]")) return "object";
|
||||
return "text";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -20,13 +20,13 @@ Comprehensive patterns for Next.js 14+ App Router architecture, Server Component
|
||||
|
||||
### 1. Rendering Modes
|
||||
|
||||
| Mode | Where | When to Use |
|
||||
|------|-------|-------------|
|
||||
| **Server Components** | Server only | Data fetching, heavy computation, secrets |
|
||||
| **Client Components** | Browser | Interactivity, hooks, browser APIs |
|
||||
| **Static** | Build time | Content that rarely changes |
|
||||
| **Dynamic** | Request time | Personalized or real-time data |
|
||||
| **Streaming** | Progressive | Large pages, slow data sources |
|
||||
| Mode | Where | When to Use |
|
||||
| --------------------- | ------------ | ----------------------------------------- |
|
||||
| **Server Components** | Server only | Data fetching, heavy computation, secrets |
|
||||
| **Client Components** | Browser | Interactivity, hooks, browser APIs |
|
||||
| **Static** | Build time | Content that rarely changes |
|
||||
| **Dynamic** | Request time | Personalized or real-time data |
|
||||
| **Streaming** | Progressive | Large pages, slow data sources |
|
||||
|
||||
### 2. File Conventions
|
||||
|
||||
@@ -199,18 +199,18 @@ export function AddToCartButton({ productId }: { productId: string }) {
|
||||
|
||||
```typescript
|
||||
// app/actions/cart.ts
|
||||
'use server'
|
||||
"use server";
|
||||
|
||||
import { revalidateTag } from 'next/cache'
|
||||
import { cookies } from 'next/headers'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { revalidateTag } from "next/cache";
|
||||
import { cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export async function addToCart(productId: string) {
|
||||
const cookieStore = await cookies()
|
||||
const sessionId = cookieStore.get('session')?.value
|
||||
const cookieStore = await cookies();
|
||||
const sessionId = cookieStore.get("session")?.value;
|
||||
|
||||
if (!sessionId) {
|
||||
redirect('/login')
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -218,29 +218,29 @@ export async function addToCart(productId: string) {
|
||||
where: { sessionId_productId: { sessionId, productId } },
|
||||
update: { quantity: { increment: 1 } },
|
||||
create: { sessionId, productId, quantity: 1 },
|
||||
})
|
||||
});
|
||||
|
||||
revalidateTag('cart')
|
||||
return { success: true }
|
||||
revalidateTag("cart");
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return { error: 'Failed to add item to cart' }
|
||||
return { error: "Failed to add item to cart" };
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkout(formData: FormData) {
|
||||
const address = formData.get('address') as string
|
||||
const payment = formData.get('payment') as string
|
||||
const address = formData.get("address") as string;
|
||||
const payment = formData.get("payment") as string;
|
||||
|
||||
// Validate
|
||||
if (!address || !payment) {
|
||||
return { error: 'Missing required fields' }
|
||||
return { error: "Missing required fields" };
|
||||
}
|
||||
|
||||
// Process order
|
||||
const order = await processOrder({ address, payment })
|
||||
const order = await processOrder({ address, payment });
|
||||
|
||||
// Redirect to confirmation
|
||||
redirect(`/orders/${order.id}/confirmation`)
|
||||
redirect(`/orders/${order.id}/confirmation`);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -401,46 +401,43 @@ async function Recommendations({ productId }: { productId: string }) {
|
||||
|
||||
```typescript
|
||||
// app/api/products/route.ts
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const searchParams = request.nextUrl.searchParams
|
||||
const category = searchParams.get('category')
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const category = searchParams.get("category");
|
||||
|
||||
const products = await db.product.findMany({
|
||||
where: category ? { category } : undefined,
|
||||
take: 20,
|
||||
})
|
||||
});
|
||||
|
||||
return NextResponse.json(products)
|
||||
return NextResponse.json(products);
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.json()
|
||||
const body = await request.json();
|
||||
|
||||
const product = await db.product.create({
|
||||
data: body,
|
||||
})
|
||||
});
|
||||
|
||||
return NextResponse.json(product, { status: 201 })
|
||||
return NextResponse.json(product, { status: 201 });
|
||||
}
|
||||
|
||||
// app/api/products/[id]/route.ts
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
const { id } = await params
|
||||
const product = await db.product.findUnique({ where: { id } })
|
||||
const { id } = await params;
|
||||
const product = await db.product.findUnique({ where: { id } });
|
||||
|
||||
if (!product) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Product not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
return NextResponse.json({ error: "Product not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json(product)
|
||||
return NextResponse.json(product);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -499,31 +496,32 @@ export default async function ProductPage({ params }: Props) {
|
||||
|
||||
```typescript
|
||||
// No cache (always fresh)
|
||||
fetch(url, { cache: 'no-store' })
|
||||
fetch(url, { cache: "no-store" });
|
||||
|
||||
// Cache forever (static)
|
||||
fetch(url, { cache: 'force-cache' })
|
||||
fetch(url, { cache: "force-cache" });
|
||||
|
||||
// ISR - revalidate after 60 seconds
|
||||
fetch(url, { next: { revalidate: 60 } })
|
||||
fetch(url, { next: { revalidate: 60 } });
|
||||
|
||||
// Tag-based invalidation
|
||||
fetch(url, { next: { tags: ['products'] } })
|
||||
fetch(url, { next: { tags: ["products"] } });
|
||||
|
||||
// Invalidate via Server Action
|
||||
'use server'
|
||||
import { revalidateTag, revalidatePath } from 'next/cache'
|
||||
("use server");
|
||||
import { revalidateTag, revalidatePath } from "next/cache";
|
||||
|
||||
export async function updateProduct(id: string, data: ProductData) {
|
||||
await db.product.update({ where: { id }, data })
|
||||
revalidateTag('products')
|
||||
revalidatePath('/products')
|
||||
await db.product.update({ where: { id }, data });
|
||||
revalidateTag("products");
|
||||
revalidatePath("/products");
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
|
||||
- **Start with Server Components** - Add 'use client' only when needed
|
||||
- **Colocate data fetching** - Fetch data where it's used
|
||||
- **Use Suspense boundaries** - Enable streaming for slow data
|
||||
@@ -531,6 +529,7 @@ export async function updateProduct(id: string, data: ProductData) {
|
||||
- **Use Server Actions** - For mutations with progressive enhancement
|
||||
|
||||
### Don'ts
|
||||
|
||||
- **Don't pass serializable data** - Server → Client boundary limitations
|
||||
- **Don't use hooks in Server Components** - No useState, useEffect
|
||||
- **Don't fetch in Client Components** - Use Server Components or React Query
|
||||
|
||||
@@ -38,13 +38,13 @@ src/
|
||||
|
||||
### 2. Expo vs Bare React Native
|
||||
|
||||
| Feature | Expo | Bare RN |
|
||||
|---------|------|---------|
|
||||
| Setup complexity | Low | High |
|
||||
| Native modules | EAS Build | Manual linking |
|
||||
| OTA updates | Built-in | Manual setup |
|
||||
| Build service | EAS | Custom CI |
|
||||
| Custom native code | Config plugins | Direct access |
|
||||
| Feature | Expo | Bare RN |
|
||||
| ------------------ | -------------- | -------------- |
|
||||
| Setup complexity | Low | High |
|
||||
| Native modules | EAS Build | Manual linking |
|
||||
| OTA updates | Built-in | Manual setup |
|
||||
| Build service | EAS | Custom CI |
|
||||
| Custom native code | Config plugins | Direct access |
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -332,60 +332,60 @@ export function useCreateProduct() {
|
||||
|
||||
```typescript
|
||||
// services/haptics.ts
|
||||
import * as Haptics from 'expo-haptics'
|
||||
import { Platform } from 'react-native'
|
||||
import * as Haptics from "expo-haptics";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
export const haptics = {
|
||||
light: () => {
|
||||
if (Platform.OS !== 'web') {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
|
||||
if (Platform.OS !== "web") {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
}
|
||||
},
|
||||
medium: () => {
|
||||
if (Platform.OS !== 'web') {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium)
|
||||
if (Platform.OS !== "web") {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
|
||||
}
|
||||
},
|
||||
heavy: () => {
|
||||
if (Platform.OS !== 'web') {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy)
|
||||
if (Platform.OS !== "web") {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy);
|
||||
}
|
||||
},
|
||||
success: () => {
|
||||
if (Platform.OS !== 'web') {
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
|
||||
if (Platform.OS !== "web") {
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
if (Platform.OS !== 'web') {
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error)
|
||||
if (Platform.OS !== "web") {
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// services/biometrics.ts
|
||||
import * as LocalAuthentication from 'expo-local-authentication'
|
||||
import * as LocalAuthentication from "expo-local-authentication";
|
||||
|
||||
export async function authenticateWithBiometrics(): Promise<boolean> {
|
||||
const hasHardware = await LocalAuthentication.hasHardwareAsync()
|
||||
if (!hasHardware) return false
|
||||
const hasHardware = await LocalAuthentication.hasHardwareAsync();
|
||||
if (!hasHardware) return false;
|
||||
|
||||
const isEnrolled = await LocalAuthentication.isEnrolledAsync()
|
||||
if (!isEnrolled) return false
|
||||
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
|
||||
if (!isEnrolled) return false;
|
||||
|
||||
const result = await LocalAuthentication.authenticateAsync({
|
||||
promptMessage: 'Authenticate to continue',
|
||||
fallbackLabel: 'Use passcode',
|
||||
promptMessage: "Authenticate to continue",
|
||||
fallbackLabel: "Use passcode",
|
||||
disableDeviceFallback: false,
|
||||
})
|
||||
});
|
||||
|
||||
return result.success
|
||||
return result.success;
|
||||
}
|
||||
|
||||
// services/notifications.ts
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import { Platform } from 'react-native'
|
||||
import Constants from 'expo-constants'
|
||||
import * as Notifications from "expo-notifications";
|
||||
import { Platform } from "react-native";
|
||||
import Constants from "expo-constants";
|
||||
|
||||
Notifications.setNotificationHandler({
|
||||
handleNotification: async () => ({
|
||||
@@ -393,35 +393,35 @@ Notifications.setNotificationHandler({
|
||||
shouldPlaySound: true,
|
||||
shouldSetBadge: true,
|
||||
}),
|
||||
})
|
||||
});
|
||||
|
||||
export async function registerForPushNotifications() {
|
||||
let token: string | undefined
|
||||
let token: string | undefined;
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
await Notifications.setNotificationChannelAsync('default', {
|
||||
name: 'default',
|
||||
if (Platform.OS === "android") {
|
||||
await Notifications.setNotificationChannelAsync("default", {
|
||||
name: "default",
|
||||
importance: Notifications.AndroidImportance.MAX,
|
||||
vibrationPattern: [0, 250, 250, 250],
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const { status: existingStatus } = await Notifications.getPermissionsAsync()
|
||||
let finalStatus = existingStatus
|
||||
const { status: existingStatus } = await Notifications.getPermissionsAsync();
|
||||
let finalStatus = existingStatus;
|
||||
|
||||
if (existingStatus !== 'granted') {
|
||||
const { status } = await Notifications.requestPermissionsAsync()
|
||||
finalStatus = status
|
||||
if (existingStatus !== "granted") {
|
||||
const { status } = await Notifications.requestPermissionsAsync();
|
||||
finalStatus = status;
|
||||
}
|
||||
|
||||
if (finalStatus !== 'granted') {
|
||||
return null
|
||||
if (finalStatus !== "granted") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const projectId = Constants.expoConfig?.extra?.eas?.projectId
|
||||
token = (await Notifications.getExpoPushTokenAsync({ projectId })).data
|
||||
const projectId = Constants.expoConfig?.extra?.eas?.projectId;
|
||||
token = (await Notifications.getExpoPushTokenAsync({ projectId })).data;
|
||||
|
||||
return token
|
||||
return token;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -650,6 +650,7 @@ eas update --branch production --message "Bug fixes"
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
|
||||
- **Use Expo** - Faster development, OTA updates, managed native code
|
||||
- **FlashList over FlatList** - Better performance for long lists
|
||||
- **Memoize components** - Prevent unnecessary re-renders
|
||||
@@ -657,6 +658,7 @@ eas update --branch production --message "Bug fixes"
|
||||
- **Test on real devices** - Simulators miss real-world issues
|
||||
|
||||
### Don'ts
|
||||
|
||||
- **Don't inline styles** - Use StyleSheet.create for performance
|
||||
- **Don't fetch in render** - Use useEffect or React Query
|
||||
- **Don't ignore platform differences** - Test on both iOS and Android
|
||||
|
||||
@@ -20,13 +20,13 @@ Comprehensive guide to modern React state management patterns, from local compon
|
||||
|
||||
### 1. State Categories
|
||||
|
||||
| Type | Description | Solutions |
|
||||
|------|-------------|-----------|
|
||||
| **Local State** | Component-specific, UI state | useState, useReducer |
|
||||
| **Global State** | Shared across components | Redux Toolkit, Zustand, Jotai |
|
||||
| **Server State** | Remote data, caching | React Query, SWR, RTK Query |
|
||||
| **URL State** | Route parameters, search | React Router, nuqs |
|
||||
| **Form State** | Input values, validation | React Hook Form, Formik |
|
||||
| Type | Description | Solutions |
|
||||
| ---------------- | ---------------------------- | ----------------------------- |
|
||||
| **Local State** | Component-specific, UI state | useState, useReducer |
|
||||
| **Global State** | Shared across components | Redux Toolkit, Zustand, Jotai |
|
||||
| **Server State** | Remote data, caching | React Query, SWR, RTK Query |
|
||||
| **URL State** | Route parameters, search | React Router, nuqs |
|
||||
| **Form State** | Input values, validation | React Hook Form, Formik |
|
||||
|
||||
### 2. Selection Criteria
|
||||
|
||||
@@ -87,10 +87,10 @@ function Header() {
|
||||
|
||||
```typescript
|
||||
// store/index.ts
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
|
||||
import userReducer from './slices/userSlice'
|
||||
import cartReducer from './slices/cartSlice'
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||
import userReducer from "./slices/userSlice";
|
||||
import cartReducer from "./slices/cartSlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
@@ -100,99 +100,99 @@ export const store = configureStore({
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
serializableCheck: {
|
||||
ignoredActions: ['persist/PERSIST'],
|
||||
ignoredActions: ["persist/PERSIST"],
|
||||
},
|
||||
}),
|
||||
})
|
||||
});
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>
|
||||
export type AppDispatch = typeof store.dispatch
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
|
||||
// Typed hooks
|
||||
export const useAppDispatch: () => AppDispatch = useDispatch
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
||||
export const useAppDispatch: () => AppDispatch = useDispatch;
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||
```
|
||||
|
||||
```typescript
|
||||
// store/slices/userSlice.ts
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface User {
|
||||
id: string
|
||||
email: string
|
||||
name: string
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UserState {
|
||||
current: User | null
|
||||
status: 'idle' | 'loading' | 'succeeded' | 'failed'
|
||||
error: string | null
|
||||
current: User | null;
|
||||
status: "idle" | "loading" | "succeeded" | "failed";
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const initialState: UserState = {
|
||||
current: null,
|
||||
status: 'idle',
|
||||
status: "idle",
|
||||
error: null,
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchUser = createAsyncThunk(
|
||||
'user/fetchUser',
|
||||
"user/fetchUser",
|
||||
async (userId: string, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await fetch(`/api/users/${userId}`)
|
||||
if (!response.ok) throw new Error('Failed to fetch user')
|
||||
return await response.json()
|
||||
const response = await fetch(`/api/users/${userId}`);
|
||||
if (!response.ok) throw new Error("Failed to fetch user");
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return rejectWithValue((error as Error).message)
|
||||
return rejectWithValue((error as Error).message);
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
const userSlice = createSlice({
|
||||
name: 'user',
|
||||
name: "user",
|
||||
initialState,
|
||||
reducers: {
|
||||
setUser: (state, action: PayloadAction<User>) => {
|
||||
state.current = action.payload
|
||||
state.status = 'succeeded'
|
||||
state.current = action.payload;
|
||||
state.status = "succeeded";
|
||||
},
|
||||
clearUser: (state) => {
|
||||
state.current = null
|
||||
state.status = 'idle'
|
||||
state.current = null;
|
||||
state.status = "idle";
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(fetchUser.pending, (state) => {
|
||||
state.status = 'loading'
|
||||
state.error = null
|
||||
state.status = "loading";
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchUser.fulfilled, (state, action) => {
|
||||
state.status = 'succeeded'
|
||||
state.current = action.payload
|
||||
state.status = "succeeded";
|
||||
state.current = action.payload;
|
||||
})
|
||||
.addCase(fetchUser.rejected, (state, action) => {
|
||||
state.status = 'failed'
|
||||
state.error = action.payload as string
|
||||
})
|
||||
state.status = "failed";
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
export const { setUser, clearUser } = userSlice.actions
|
||||
export default userSlice.reducer
|
||||
export const { setUser, clearUser } = userSlice.actions;
|
||||
export default userSlice.reducer;
|
||||
```
|
||||
|
||||
### Pattern 2: Zustand with Slices (Scalable)
|
||||
|
||||
```typescript
|
||||
// store/slices/createUserSlice.ts
|
||||
import { StateCreator } from 'zustand'
|
||||
import { StateCreator } from "zustand";
|
||||
|
||||
export interface UserSlice {
|
||||
user: User | null
|
||||
isAuthenticated: boolean
|
||||
login: (credentials: Credentials) => Promise<void>
|
||||
logout: () => void
|
||||
user: User | null;
|
||||
isAuthenticated: boolean;
|
||||
login: (credentials: Credentials) => Promise<void>;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
export const createUserSlice: StateCreator<
|
||||
@@ -204,31 +204,31 @@ export const createUserSlice: StateCreator<
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
login: async (credentials) => {
|
||||
const user = await authApi.login(credentials)
|
||||
set({ user, isAuthenticated: true })
|
||||
const user = await authApi.login(credentials);
|
||||
set({ user, isAuthenticated: true });
|
||||
},
|
||||
logout: () => {
|
||||
set({ user: null, isAuthenticated: false })
|
||||
set({ user: null, isAuthenticated: false });
|
||||
// Can access other slices
|
||||
// get().clearCart()
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
// store/index.ts
|
||||
import { create } from 'zustand'
|
||||
import { createUserSlice, UserSlice } from './slices/createUserSlice'
|
||||
import { createCartSlice, CartSlice } from './slices/createCartSlice'
|
||||
import { create } from "zustand";
|
||||
import { createUserSlice, UserSlice } from "./slices/createUserSlice";
|
||||
import { createCartSlice, CartSlice } from "./slices/createCartSlice";
|
||||
|
||||
type StoreState = UserSlice & CartSlice
|
||||
type StoreState = UserSlice & CartSlice;
|
||||
|
||||
export const useStore = create<StoreState>()((...args) => ({
|
||||
...createUserSlice(...args),
|
||||
...createCartSlice(...args),
|
||||
}))
|
||||
}));
|
||||
|
||||
// Selective subscriptions (prevents unnecessary re-renders)
|
||||
export const useUser = () => useStore((state) => state.user)
|
||||
export const useCart = () => useStore((state) => state.cart)
|
||||
export const useUser = () => useStore((state) => state.user);
|
||||
export const useCart = () => useStore((state) => state.cart);
|
||||
```
|
||||
|
||||
### Pattern 3: Jotai for Atomic State
|
||||
@@ -280,16 +280,16 @@ function Profile() {
|
||||
|
||||
```typescript
|
||||
// hooks/useUsers.ts
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
// Query keys factory
|
||||
export const userKeys = {
|
||||
all: ['users'] as const,
|
||||
lists: () => [...userKeys.all, 'list'] as const,
|
||||
all: ["users"] as const,
|
||||
lists: () => [...userKeys.all, "list"] as const,
|
||||
list: (filters: UserFilters) => [...userKeys.lists(), filters] as const,
|
||||
details: () => [...userKeys.all, 'detail'] as const,
|
||||
details: () => [...userKeys.all, "detail"] as const,
|
||||
detail: (id: string) => [...userKeys.details(), id] as const,
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch hook
|
||||
export function useUsers(filters: UserFilters) {
|
||||
@@ -298,7 +298,7 @@ export function useUsers(filters: UserFilters) {
|
||||
queryFn: () => fetchUsers(filters),
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
gcTime: 30 * 60 * 1000, // 30 minutes (formerly cacheTime)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Single user hook
|
||||
@@ -307,39 +307,45 @@ export function useUser(id: string) {
|
||||
queryKey: userKeys.detail(id),
|
||||
queryFn: () => fetchUser(id),
|
||||
enabled: !!id, // Don't fetch if no id
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Mutation with optimistic update
|
||||
export function useUpdateUser() {
|
||||
const queryClient = useQueryClient()
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: updateUser,
|
||||
onMutate: async (newUser) => {
|
||||
// Cancel outgoing refetches
|
||||
await queryClient.cancelQueries({ queryKey: userKeys.detail(newUser.id) })
|
||||
await queryClient.cancelQueries({
|
||||
queryKey: userKeys.detail(newUser.id),
|
||||
});
|
||||
|
||||
// Snapshot previous value
|
||||
const previousUser = queryClient.getQueryData(userKeys.detail(newUser.id))
|
||||
const previousUser = queryClient.getQueryData(
|
||||
userKeys.detail(newUser.id),
|
||||
);
|
||||
|
||||
// Optimistically update
|
||||
queryClient.setQueryData(userKeys.detail(newUser.id), newUser)
|
||||
queryClient.setQueryData(userKeys.detail(newUser.id), newUser);
|
||||
|
||||
return { previousUser }
|
||||
return { previousUser };
|
||||
},
|
||||
onError: (err, newUser, context) => {
|
||||
// Rollback on error
|
||||
queryClient.setQueryData(
|
||||
userKeys.detail(newUser.id),
|
||||
context?.previousUser
|
||||
)
|
||||
context?.previousUser,
|
||||
);
|
||||
},
|
||||
onSettled: (data, error, variables) => {
|
||||
// Refetch after mutation
|
||||
queryClient.invalidateQueries({ queryKey: userKeys.detail(variables.id) })
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: userKeys.detail(variables.id),
|
||||
});
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
@@ -378,6 +384,7 @@ function Dashboard() {
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
|
||||
- **Colocate state** - Keep state as close to where it's used as possible
|
||||
- **Use selectors** - Prevent unnecessary re-renders with selective subscriptions
|
||||
- **Normalize data** - Flatten nested structures for easier updates
|
||||
@@ -385,6 +392,7 @@ function Dashboard() {
|
||||
- **Separate concerns** - Server state (React Query) vs client state (Zustand)
|
||||
|
||||
### Don'ts
|
||||
|
||||
- **Don't over-globalize** - Not everything needs to be in global state
|
||||
- **Don't duplicate server state** - Let React Query manage it
|
||||
- **Don't mutate directly** - Always use immutable updates
|
||||
@@ -397,28 +405,28 @@ function Dashboard() {
|
||||
|
||||
```typescript
|
||||
// Before (legacy Redux)
|
||||
const ADD_TODO = 'ADD_TODO'
|
||||
const addTodo = (text) => ({ type: ADD_TODO, payload: text })
|
||||
const ADD_TODO = "ADD_TODO";
|
||||
const addTodo = (text) => ({ type: ADD_TODO, payload: text });
|
||||
function todosReducer(state = [], action) {
|
||||
switch (action.type) {
|
||||
case ADD_TODO:
|
||||
return [...state, { text: action.payload, completed: false }]
|
||||
return [...state, { text: action.payload, completed: false }];
|
||||
default:
|
||||
return state
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// After (Redux Toolkit)
|
||||
const todosSlice = createSlice({
|
||||
name: 'todos',
|
||||
name: "todos",
|
||||
initialState: [],
|
||||
reducers: {
|
||||
addTodo: (state, action: PayloadAction<string>) => {
|
||||
// Immer allows "mutations"
|
||||
state.push({ text: action.payload, completed: false })
|
||||
state.push({ text: action.payload, completed: false });
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
@@ -39,51 +39,51 @@ Base styles → Variants → Sizes → States → Overrides
|
||||
|
||||
```typescript
|
||||
// tailwind.config.ts
|
||||
import type { Config } from 'tailwindcss'
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
const config: Config = {
|
||||
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
|
||||
darkMode: 'class',
|
||||
content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// Semantic color tokens
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))',
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))',
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))',
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
border: 'hsl(var(--border))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
border: "hsl(var(--border))",
|
||||
ring: "hsl(var(--ring))",
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('tailwindcss-animate')],
|
||||
}
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
};
|
||||
|
||||
export default config
|
||||
export default config;
|
||||
```
|
||||
|
||||
```css
|
||||
@@ -625,26 +625,27 @@ export function ThemeToggle() {
|
||||
|
||||
```typescript
|
||||
// lib/utils.ts
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
// Focus ring utility
|
||||
export const focusRing = cn(
|
||||
'focus-visible:outline-none focus-visible:ring-2',
|
||||
'focus-visible:ring-ring focus-visible:ring-offset-2'
|
||||
)
|
||||
"focus-visible:outline-none focus-visible:ring-2",
|
||||
"focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
);
|
||||
|
||||
// Disabled utility
|
||||
export const disabled = 'disabled:pointer-events-none disabled:opacity-50'
|
||||
export const disabled = "disabled:pointer-events-none disabled:opacity-50";
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
|
||||
- **Use CSS variables** - Enable runtime theming
|
||||
- **Compose with CVA** - Type-safe variants
|
||||
- **Use semantic colors** - `primary` not `blue-500`
|
||||
@@ -652,6 +653,7 @@ export const disabled = 'disabled:pointer-events-none disabled:opacity-50'
|
||||
- **Add accessibility** - ARIA attributes, focus states
|
||||
|
||||
### Don'ts
|
||||
|
||||
- **Don't use arbitrary values** - Extend theme instead
|
||||
- **Don't nest @apply** - Hurts readability
|
||||
- **Don't skip focus states** - Keyboard users need them
|
||||
|
||||
Reference in New Issue
Block a user