--- name: react-vite description: React 18+ patterns with Vite bundler, TypeScript, hooks, component design, and state management. Activate for React apps, Vite configuration, TypeScript, frontend development, and modern React patterns. allowed-tools: - Bash - Read - Write - Edit - Glob - Grep --- # React Vite Skill Provides comprehensive React 18+ development with Vite bundler, TypeScript integration, and modern frontend patterns. ## When to Use This Skill Activate this skill when working with: - React 18+ applications with Vite - TypeScript component development - React hooks and custom hooks - State management (Context, Zustand, React Query) - Component composition and patterns - Vite configuration and optimization - Build optimization and deployment ## Quick Reference ### Project Setup ```bash # Create new Vite + React + TypeScript project npm create vite@latest my-app -- --template react-ts cd my-app npm install # Install common dependencies npm install @tanstack/react-query zustand react-hook-form zod npm install -D @types/node # Development npm run dev # Build npm run build npm run preview # Lint npm run lint ``` ## Project Structure ``` src/ ├── main.tsx # Application entry point ├── App.tsx # Root component ├── vite-env.d.ts # Vite TypeScript definitions ├── components/ # Reusable components │ ├── ui/ # Base UI components │ │ ├── Button.tsx │ │ ├── Card.tsx │ │ └── Input.tsx │ └── features/ # Feature-specific components │ ├── UserList.tsx │ └── Dashboard.tsx ├── hooks/ # Custom hooks │ ├── useAuth.ts │ ├── useApi.ts │ └── useLocalStorage.ts ├── stores/ # State management │ ├── authStore.ts │ └── userStore.ts ├── services/ # API and external services │ ├── api.ts │ └── auth.ts ├── types/ # TypeScript types │ ├── index.ts │ └── api.ts ├── utils/ # Utility functions │ └── helpers.ts └── styles/ # Global styles └── index.css ``` ## Vite Configuration ```typescript // vite.config.ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import path from 'path' export default defineConfig({ plugins: [react()], // Path aliases resolve: { alias: { '@': path.resolve(__dirname, './src'), '@components': path.resolve(__dirname, './src/components'), '@hooks': path.resolve(__dirname, './src/hooks'), '@stores': path.resolve(__dirname, './src/stores'), '@types': path.resolve(__dirname, './src/types'), '@utils': path.resolve(__dirname, './src/utils'), }, }, // Server configuration server: { port: 3000, proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true, }, }, }, // Build optimization build: { rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], utils: ['@tanstack/react-query', 'zustand'], }, }, }, sourcemap: true, minify: 'terser', }, }) ``` ## TypeScript Configuration ```json // tsconfig.json { "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "baseUrl": ".", "paths": { "@/*": ["./src/*"], "@components/*": ["./src/components/*"], "@hooks/*": ["./src/hooks/*"], "@stores/*": ["./src/stores/*"], "@types/*": ["./src/types/*"], "@utils/*": ["./src/utils/*"] } }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] } ``` ## Component Patterns ### Functional Component with TypeScript ```typescript import { FC, ReactNode } from 'react' interface ButtonProps { children: ReactNode variant?: 'primary' | 'secondary' | 'danger' size?: 'sm' | 'md' | 'lg' disabled?: boolean onClick?: () => void } export const Button: FC = ({ children, variant = 'primary', size = 'md', disabled = false, onClick, }) => { return ( ) } ``` ### Component with Generic Types ```typescript interface ListProps { items: T[] renderItem: (item: T) => ReactNode keyExtractor: (item: T) => string | number } export function List({ items, renderItem, keyExtractor }: ListProps) { return (
    {items.map((item) => (
  • {renderItem(item)}
  • ))}
) } // Usage } keyExtractor={(user) => user.id} /> ``` ## Custom Hooks ### useLocalStorage Hook ```typescript import { useState, useEffect } from 'react' export function useLocalStorage( key: string, initialValue: T ): [T, (value: T) => void] { const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key) return item ? JSON.parse(item) : initialValue } catch (error) { console.error(error) return initialValue } }) const setValue = (value: T) => { try { setStoredValue(value) window.localStorage.setItem(key, JSON.stringify(value)) } catch (error) { console.error(error) } } return [storedValue, setValue] } ``` ### useDebounce Hook ```typescript import { useState, useEffect } from 'react' export function useDebounce(value: T, delay: number = 500): T { const [debouncedValue, setDebouncedValue] = useState(value) useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value) }, delay) return () => { clearTimeout(handler) } }, [value, delay]) return debouncedValue } ``` ### useAsync Hook ```typescript import { useState, useEffect, useCallback } from 'react' interface AsyncState { data: T | null loading: boolean error: Error | null } export function useAsync( asyncFunction: () => Promise, immediate = true ) { const [state, setState] = useState>({ data: null, loading: immediate, error: null, }) const execute = useCallback(async () => { setState({ data: null, loading: true, error: null }) try { const response = await asyncFunction() setState({ data: response, loading: false, error: null }) } catch (error) { setState({ data: null, loading: false, error: error as Error }) } }, [asyncFunction]) useEffect(() => { if (immediate) { execute() } }, [execute, immediate]) return { ...state, execute } } ``` ## State Management with Zustand ```typescript // stores/authStore.ts import { create } from 'zustand' import { persist } from 'zustand/middleware' interface User { id: string email: string name: string } interface AuthState { user: User | null token: string | null isAuthenticated: boolean login: (user: User, token: string) => void logout: () => void } export const useAuthStore = create()( persist( (set) => ({ user: null, token: null, isAuthenticated: false, login: (user, token) => set({ user, token, isAuthenticated: true }), logout: () => set({ user: null, token: null, isAuthenticated: false }), }), { name: 'auth-storage', } ) ) // Usage in component function Profile() { const { user, logout } = useAuthStore() return (

{user?.name}

) } ``` ## Data Fetching with React Query ```typescript // services/api.ts import axios from 'axios' const api = axios.create({ baseURL: import.meta.env.VITE_API_URL || '/api', }) api.interceptors.request.use((config) => { const token = useAuthStore.getState().token if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) export interface User { id: number name: string email: string } export const userApi = { getAll: () => api.get('/users').then((res) => res.data), getById: (id: number) => api.get(`/users/${id}`).then((res) => res.data), create: (data: Omit) => api.post('/users', data).then((res) => res.data), update: (id: number, data: Partial) => api.patch(`/users/${id}`, data).then((res) => res.data), delete: (id: number) => api.delete(`/users/${id}`), } // hooks/useUsers.ts import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { userApi, User } from '@/services/api' export function useUsers() { return useQuery({ queryKey: ['users'], queryFn: userApi.getAll, staleTime: 5 * 60 * 1000, // 5 minutes }) } export function useUser(id: number) { return useQuery({ queryKey: ['users', id], queryFn: () => userApi.getById(id), enabled: !!id, }) } export function useCreateUser() { const queryClient = useQueryClient() return useMutation({ mutationFn: userApi.create, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }) }, }) } export function useUpdateUser() { const queryClient = useQueryClient() return useMutation({ mutationFn: ({ id, data }: { id: number; data: Partial }) => userApi.update(id, data), onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ['users'] }) queryClient.invalidateQueries({ queryKey: ['users', variables.id] }) }, }) } ``` ## Form Handling with React Hook Form ```typescript import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' const userSchema = z.object({ name: z.string().min(1, 'Name is required'), email: z.string().email('Invalid email address'), age: z.number().min(18, 'Must be at least 18'), }) type UserFormData = z.infer export function UserForm() { const { register, handleSubmit, formState: { errors, isSubmitting }, reset, } = useForm({ resolver: zodResolver(userSchema), defaultValues: { name: '', email: '', age: 18, }, }) const { mutate: createUser } = useCreateUser() const onSubmit = (data: UserFormData) => { createUser(data, { onSuccess: () => { reset() }, }) } return (
{errors.name && {errors.name.message}}
{errors.email && {errors.email.message}}
{errors.age && {errors.age.message}}
) } ``` ## Performance Optimization ### Code Splitting with Lazy Loading ```typescript import { lazy, Suspense } from 'react' import { BrowserRouter, Routes, Route } from 'react-router-dom' const Dashboard = lazy(() => import('./pages/Dashboard')) const Users = lazy(() => import('./pages/Users')) const Settings = lazy(() => import('./pages/Settings')) function App() { return ( Loading...}> } /> } /> } /> ) } ``` ### Memoization ```typescript import { memo, useMemo, useCallback } from 'react' interface UserListProps { users: User[] onUserClick: (id: number) => void } export const UserList = memo(({ users, onUserClick }) => { const sortedUsers = useMemo( () => [...users].sort((a, b) => a.name.localeCompare(b.name)), [users] ) const handleClick = useCallback( (id: number) => { console.log('Clicked user:', id) onUserClick(id) }, [onUserClick] ) return (
    {sortedUsers.map((user) => (
  • handleClick(user.id)}> {user.name}
  • ))}
) }) ``` ## Environment Variables ```bash # .env VITE_API_URL=http://localhost:8000/api VITE_APP_NAME=Zenith VITE_ENABLE_ANALYTICS=true ``` ```typescript // Access in code const apiUrl = import.meta.env.VITE_API_URL const appName = import.meta.env.VITE_APP_NAME const isDev = import.meta.env.DEV const isProd = import.meta.env.PROD ``` ## Testing Setup ```typescript // vitest.config.ts import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' import path from 'path' export default defineConfig({ plugins: [react()], test: { globals: true, environment: 'jsdom', setupFiles: './src/test/setup.ts', }, resolve: { alias: { '@': path.resolve(__dirname, './src'), }, }, }) // src/test/setup.ts import { expect, afterEach } from 'vitest' import { cleanup } from '@testing-library/react' import * as matchers from '@testing-library/jest-dom/matchers' expect.extend(matchers) afterEach(() => { cleanup() }) ``` ## Best Practices 1. **TypeScript**: Use strict mode and avoid `any` types 2. **Component Structure**: Keep components small and focused 3. **Custom Hooks**: Extract reusable logic into custom hooks 4. **State Management**: Use appropriate tools (Context for simple, Zustand/Redux for complex) 5. **Data Fetching**: Use React Query for server state management 6. **Forms**: Leverage React Hook Form with Zod validation 7. **Performance**: Use `memo`, `useMemo`, `useCallback` judiciously 8. **Code Splitting**: Implement lazy loading for routes and heavy components 9. **Error Boundaries**: Implement error boundaries for graceful error handling 10. **Accessibility**: Use semantic HTML and ARIA attributes ## Build and Deployment ```bash # Build for production npm run build # Preview production build npm run preview # Analyze bundle size npm run build -- --mode analyze # Type checking npx tsc --noEmit ``` ## Vite Plugins ```bash # Common Vite plugins npm install -D vite-plugin-pwa npm install -D vite-plugin-compression npm install -D @vitejs/plugin-react-swc # Faster than default ```