| description |
|---|
Modern React development patterns, component design principles, and best practices for building scalable React applications |
This comprehensive guide covers modern React development patterns, component design principles, and best practices for building scalable, maintainable React applications using the latest features and ecosystem tools.
src/
├── components/ # Reusable components
│ ├── ui/ # Basic UI components (Button, Input, etc.)
│ ├── layout/ # Layout components (Header, Footer, Sidebar)
│ └── features/ # Feature-specific components
├── hooks/ # Custom hooks
├── contexts/ # React context providers
├── services/ # API calls and external services
├── utils/ # Helper functions and utilities
├── types/ # TypeScript type definitions
├── constants/ # Application constants
├── assets/ # Static assets (images, fonts, etc.)
├── styles/ # Global styles and theme
└── __tests__/ # Test files
// components/features/UserProfile/index.ts
export { UserProfile } from './UserProfile';
export { UserProfileSkeleton } from './UserProfileSkeleton';
export type { UserProfileProps } from './types';
// components/features/UserProfile/UserProfile.tsx
import React from 'react';
import { UserProfileProps } from './types';
import { UserAvatar } from './UserAvatar';
import { UserDetails } from './UserDetails';
import styles from './UserProfile.module.css';
export const UserProfile: React.FC<UserProfileProps> = ({ user, onEdit }) => {
return (
<div className={styles.container}>
<UserAvatar src={user.avatar} alt={user.name} />
<UserDetails user={user} onEdit={onEdit} />
</div>
);
};import React, { useState, useEffect, useCallback } from 'react';
interface User {
id: string;
name: string;
email: string;
isActive: boolean;
}
interface UserCardProps {
user: User;
onUserUpdate?: (user: User) => void;
onUserDelete?: (userId: string) => void;
className?: string;
children?: React.ReactNode;
}
export const UserCard: React.FC<UserCardProps> = ({
user,
onUserUpdate,
onUserDelete,
className = '',
children
}) => {
const [isEditing, setIsEditing] = useState(false);
const [localUser, setLocalUser] = useState(user);
// Update local state when user prop changes
useEffect(() => {
setLocalUser(user);
}, [user]);
const handleSave = useCallback(() => {
onUserUpdate?.(localUser);
setIsEditing(false);
}, [localUser, onUserUpdate]);
const handleCancel = useCallback(() => {
setLocalUser(user);
setIsEditing(false);
}, [user]);
return (
<div className={`user-card ${className}`}>
{isEditing ? (
<UserEditForm
user={localUser}
onChange={setLocalUser}
onSave={handleSave}
onCancel={handleCancel}
/>
) : (
<UserDisplay
user={localUser}
onEdit={() => setIsEditing(true)}
onDelete={() => onUserDelete?.(user.id)}
/>
)}
{children}
</div>
);
};import React, { createContext, useContext } from 'react';
interface TabsContextValue {
activeTab: string;
setActiveTab: (tab: string) => void;
}
const TabsContext = createContext<TabsContextValue | null>(null);
const useTabs = () => {
const context = useContext(TabsContext);
if (!context) {
throw new Error('Tab components must be used within Tabs');
}
return context;
};
interface TabsProps {
defaultTab: string;
children: React.ReactNode;
}
export const Tabs: React.FC<TabsProps> = ({ defaultTab, children }) => {
const [activeTab, setActiveTab] = useState(defaultTab);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
};
const TabList: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="tab-list" role="tablist">
{children}
</div>
);
interface TabProps {
value: string;
children: React.ReactNode;
}
const Tab: React.FC<TabProps> = ({ value, children }) => {
const { activeTab, setActiveTab } = useTabs();
return (
<button
className={`tab ${activeTab === value ? 'active' : ''}`}
onClick={() => setActiveTab(value)}
role="tab"
aria-selected={activeTab === value}
>
{children}
</button>
);
};
const TabPanels: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="tab-panels">{children}</div>
);
const TabPanel: React.FC<TabProps> = ({ value, children }) => {
const { activeTab } = useTabs();
if (activeTab !== value) return null;
return (
<div className="tab-panel" role="tabpanel">
{children}
</div>
);
};
// Export compound component
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panels = TabPanels;
Tabs.Panel = TabPanel;
// Usage
<Tabs defaultTab="profile">
<Tabs.List>
<Tabs.Tab value="profile">Profile</Tabs.Tab>
<Tabs.Tab value="settings">Settings</Tabs.Tab>
</Tabs.List>
<Tabs.Panels>
<Tabs.Panel value="profile">Profile content</Tabs.Panel>
<Tabs.Panel value="settings">Settings content</Tabs.Panel>
</Tabs.Panels>
</Tabs>import { useState, useEffect, useCallback, useRef } from 'react';
interface UseAsyncState<T> {
data: T | null;
loading: boolean;
error: Error | null;
refetch: () => void;
}
export function useAsync<T>(
asyncFunction: () => Promise<T>,
dependencies: React.DependencyList = []
): UseAsyncState<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const cancelRef = useRef<boolean>(false);
const execute = useCallback(async () => {
cancelRef.current = false;
setLoading(true);
setError(null);
try {
const result = await asyncFunction();
if (!cancelRef.current) {
setData(result);
}
} catch (err) {
if (!cancelRef.current) {
setError(err instanceof Error ? err : new Error('Unknown error'));
}
} finally {
if (!cancelRef.current) {
setLoading(false);
}
}
}, dependencies);
useEffect(() => {
execute();
return () => {
cancelRef.current = true;
};
}, [execute]);
const refetch = useCallback(() => {
execute();
}, [execute]);
return { data, loading, error, refetch };
}
// Usage
const UserProfile: React.FC<{ userId: string }> = ({ userId }) => {
const { data: user, loading, error, refetch } = useAsync(
() => fetchUser(userId),
[userId]
);
if (loading) return <UserSkeleton />;
if (error) return <ErrorMessage error={error} onRetry={refetch} />;
if (!user) return <UserNotFound />;
return <UserCard user={user} />;
};import { useState, useCallback } from 'react';
interface ValidationRule<T> {
validate: (value: T) => boolean;
message: string;
}
interface FieldConfig<T> {
initialValue: T;
validationRules?: ValidationRule<T>[];
}
interface FormConfig {
[key: string]: FieldConfig<any>;
}
export function useForm<T extends Record<string, any>>(config: FormConfig) {
const [values, setValues] = useState<T>(() => {
const initialValues = {} as T;
Object.keys(config).forEach(key => {
initialValues[key as keyof T] = config[key].initialValue;
});
return initialValues;
});
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
const [touched, setTouched] = useState<Partial<Record<keyof T, boolean>>>({});
const setValue = useCallback(<K extends keyof T>(
field: K,
value: T[K]
) => {
setValues(prev => ({ ...prev, [field]: value }));
// Clear error when user starts typing
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: undefined }));
}
}, [errors]);
const setFieldTouched = useCallback(<K extends keyof T>(field: K) => {
setTouched(prev => ({ ...prev, [field]: true }));
}, []);
const validateField = useCallback(<K extends keyof T>(field: K): string | null => {
const fieldConfig = config[field as string];
if (!fieldConfig?.validationRules) return null;
const value = values[field];
for (const rule of fieldConfig.validationRules) {
if (!rule.validate(value)) {
return rule.message;
}
}
return null;
}, [config, values]);
const validateForm = useCallback(() => {
const newErrors: Partial<Record<keyof T, string>> = {};
let isValid = true;
Object.keys(config).forEach(key => {
const error = validateField(key as keyof T);
if (error) {
newErrors[key as keyof T] = error;
isValid = false;
}
});
setErrors(newErrors);
return isValid;
}, [config, validateField]);
const handleSubmit = useCallback((onSubmit: (values: T) => void) => {
return (e: React.FormEvent) => {
e.preventDefault();
// Mark all fields as touched
const allTouched = {} as Record<keyof T, boolean>;
Object.keys(config).forEach(key => {
allTouched[key as keyof T] = true;
});
setTouched(allTouched);
if (validateForm()) {
onSubmit(values);
}
};
}, [values, validateForm, config]);
return {
values,
errors,
touched,
setValue,
setFieldTouched,
validateForm,
handleSubmit,
isValid: Object.keys(errors).length === 0
};
}
// Usage
const LoginForm: React.FC = () => {
const form = useForm({
email: {
initialValue: '',
validationRules: [
{
validate: (value: string) => value.length > 0,
message: 'Email is required'
},
{
validate: (value: string) => /\S+@\S+\.\S+/.test(value),
message: 'Invalid email format'
}
]
},
password: {
initialValue: '',
validationRules: [
{
validate: (value: string) => value.length >= 8,
message: 'Password must be at least 8 characters'
}
]
}
});
const handleLogin = (values: { email: string; password: string }) => {
console.log('Login with:', values);
};
return (
<form onSubmit={form.handleSubmit(handleLogin)}>
<input
type="email"
value={form.values.email}
onChange={(e) => form.setValue('email', e.target.value)}
onBlur={() => form.setFieldTouched('email')}
placeholder="Email"
/>
{form.touched.email && form.errors.email && (
<span className="error">{form.errors.email}</span>
)}
<input
type="password"
value={form.values.password}
onChange={(e) => form.setValue('password', e.target.value)}
onBlur={() => form.setFieldTouched('password')}
placeholder="Password"
/>
{form.touched.password && form.errors.password && (
<span className="error">{form.errors.password}</span>
)}
<button type="submit" disabled={!form.isValid}>
Login
</button>
</form>
);
};import React, { createContext, useContext, useReducer, useCallback } from 'react';
interface User {
id: string;
name: string;
email: string;
}
interface AuthState {
user: User | null;
isAuthenticated: boolean;
loading: boolean;
}
type AuthAction =
| { type: 'AUTH_START' }
| { type: 'AUTH_SUCCESS'; payload: User }
| { type: 'AUTH_FAILURE' }
| { type: 'LOGOUT' };
const authReducer = (state: AuthState, action: AuthAction): AuthState => {
switch (action.type) {
case 'AUTH_START':
return { ...state, loading: true };
case 'AUTH_SUCCESS':
return {
user: action.payload,
isAuthenticated: true,
loading: false
};
case 'AUTH_FAILURE':
return {
user: null,
isAuthenticated: false,
loading: false
};
case 'LOGOUT':
return {
user: null,
isAuthenticated: false,
loading: false
};
default:
return state;
}
};
interface AuthContextValue extends AuthState {
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextValue | null>(null);
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
};
interface AuthProviderProps {
children: React.ReactNode;
}
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [state, dispatch] = useReducer(authReducer, {
user: null,
isAuthenticated: false,
loading: false
});
const login = useCallback(async (email: string, password: string) => {
dispatch({ type: 'AUTH_START' });
try {
const user = await authService.login(email, password);
dispatch({ type: 'AUTH_SUCCESS', payload: user });
} catch (error) {
dispatch({ type: 'AUTH_FAILURE' });
throw error;
}
}, []);
const logout = useCallback(() => {
authService.logout();
dispatch({ type: 'LOGOUT' });
}, []);
const value: AuthContextValue = {
...state,
login,
logout
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
interface Todo {
id: string;
text: string;
completed: boolean;
createdAt: Date;
}
interface TodoState {
todos: Todo[];
filter: 'all' | 'active' | 'completed';
// Actions
addTodo: (text: string) => void;
toggleTodo: (id: string) => void;
deleteTodo: (id: string) => void;
setFilter: (filter: 'all' | 'active' | 'completed') => void;
clearCompleted: () => void;
// Selectors
filteredTodos: () => Todo[];
completedCount: () => number;
activeCount: () => number;
}
export const useTodoStore = create<TodoState>()(
devtools(
persist(
immer((set, get) => ({
todos: [],
filter: 'all',
addTodo: (text: string) =>
set((state) => {
state.todos.push({
id: crypto.randomUUID(),
text,
completed: false,
createdAt: new Date()
});
}),
toggleTodo: (id: string) =>
set((state) => {
const todo = state.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
}),
deleteTodo: (id: string) =>
set((state) => {
state.todos = state.todos.filter(t => t.id !== id);
}),
setFilter: (filter) =>
set((state) => {
state.filter = filter;
}),
clearCompleted: () =>
set((state) => {
state.todos = state.todos.filter(t => !t.completed);
}),
filteredTodos: () => {
const { todos, filter } = get();
switch (filter) {
case 'active':
return todos.filter(t => !t.completed);
case 'completed':
return todos.filter(t => t.completed);
default:
return todos;
}
},
completedCount: () => get().todos.filter(t => t.completed).length,
activeCount: () => get().todos.filter(t => !t.completed).length
})),
{
name: 'todo-storage'
}
)
)
);
// Usage in component
const TodoList: React.FC = () => {
const { filteredTodos, toggleTodo, deleteTodo } = useTodoStore();
const todos = filteredTodos();
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span className={todo.completed ? 'completed' : ''}>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
);
};import React, { memo, useMemo, useCallback, useState } from 'react';
interface ExpensiveComponentProps {
items: Array<{ id: string; name: string; value: number }>;
onItemClick: (id: string) => void;
}
// Memoize expensive calculations
const ExpensiveComponent: React.FC<ExpensiveComponentProps> = memo(({
items,
onItemClick
}) => {
// Memoize expensive computations
const expensiveValue = useMemo(() => {
console.log('Computing expensive value...');
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
// Memoize sorted items
const sortedItems = useMemo(() => {
console.log('Sorting items...');
return [...items].sort((a, b) => b.value - a.value);
}, [items]);
return (
<div>
<p>Total: {expensiveValue}</p>
<ul>
{sortedItems.map(item => (
<ExpensiveListItem
key={item.id}
item={item}
onClick={onItemClick}
/>
))}
</ul>
</div>
);
});
interface ListItemProps {
item: { id: string; name: string; value: number };
onClick: (id: string) => void;
}
const ExpensiveListItem: React.FC<ListItemProps> = memo(({ item, onClick }) => {
// Memoize the click handler to prevent re-renders
const handleClick = useCallback(() => {
onClick(item.id);
}, [item.id, onClick]);
return (
<li onClick={handleClick}>
{item.name}: {item.value}
</li>
);
});
// Parent component with optimized callbacks
const ParentComponent: React.FC = () => {
const [items, setItems] = useState([
{ id: '1', name: 'Item 1', value: 100 },
{ id: '2', name: 'Item 2', value: 200 }
]);
// Memoize callback to prevent child re-renders
const handleItemClick = useCallback((id: string) => {
console.log('Item clicked:', id);
// Handle item click logic
}, []);
return (
<ExpensiveComponent
items={items}
onItemClick={handleItemClick}
/>
);
};import React, { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { ErrorBoundary } from './components/ErrorBoundary';
import { LoadingSpinner } from './components/LoadingSpinner';
// Lazy load components
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() =>
import('./pages/Dashboard').then(module => ({
default: module.Dashboard
}))
);
// Lazy load with retry logic
const lazyWithRetry = (importFunc: () => Promise<any>) => {
return lazy(() =>
importFunc().catch(error => {
console.error('Failed to load component, retrying...', error);
// Retry logic
return importFunc();
})
);
};
const Settings = lazyWithRetry(() => import('./pages/Settings'));
const App: React.FC = () => {
return (
<BrowserRouter>
<ErrorBoundary>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</ErrorBoundary>
</BrowserRouter>
);
};
export default App;import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi } from 'vitest';
import { UserCard } from './UserCard';
const mockUser = {
id: '1',
name: 'John Doe',
email: 'john@example.com',
isActive: true
};
describe('UserCard', () => {
it('renders user information correctly', () => {
render(<UserCard user={mockUser} />);
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
expect(screen.getByText('Active')).toBeInTheDocument();
});
it('calls onUserUpdate when edit form is submitted', async () => {
const user = userEvent.setup();
const mockOnUpdate = vi.fn();
render(
<UserCard user={mockUser} onUserUpdate={mockOnUpdate} />
);
// Click edit button
await user.click(screen.getByRole('button', { name: /edit/i }));
// Update name field
const nameInput = screen.getByDisplayValue('John Doe');
await user.clear(nameInput);
await user.type(nameInput, 'Jane Doe');
// Submit form
await user.click(screen.getByRole('button', { name: /save/i }));
// Verify callback was called with updated user
expect(mockOnUpdate).toHaveBeenCalledWith({
...mockUser,
name: 'Jane Doe'
});
});
it('handles loading state during async operations', async () => {
const mockOnUpdate = vi.fn(() =>
new Promise(resolve => setTimeout(resolve, 1000))
);
render(
<UserCard user={mockUser} onUserUpdate={mockOnUpdate} />
);
const editButton = screen.getByRole('button', { name: /edit/i });
await userEvent.click(editButton);
const saveButton = screen.getByRole('button', { name: /save/i });
await userEvent.click(saveButton);
// Check loading state
expect(screen.getByText(/saving/i)).toBeInTheDocument();
expect(saveButton).toBeDisabled();
// Wait for operation to complete
await waitFor(() => {
expect(screen.queryByText(/saving/i)).not.toBeInTheDocument();
});
});
it('handles error states gracefully', async () => {
const mockOnUpdate = vi.fn(() =>
Promise.reject(new Error('Network error'))
);
render(
<UserCard user={mockUser} onUserUpdate={mockOnUpdate} />
);
const editButton = screen.getByRole('button', { name: /edit/i });
await userEvent.click(editButton);
const saveButton = screen.getByRole('button', { name: /save/i });
await userEvent.click(saveButton);
// Check error message appears
await waitFor(() => {
expect(screen.getByText(/failed to update user/i)).toBeInTheDocument();
});
});
});import { renderHook, act } from '@testing-library/react';
import { vi } from 'vitest';
import { useAsync } from './useAsync';
// Mock async function
const mockAsyncFunction = vi.fn();
describe('useAsync', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should handle successful async operation', async () => {
const expectedData = { id: 1, name: 'Test' };
mockAsyncFunction.mockResolvedValue(expectedData);
const { result } = renderHook(() =>
useAsync(mockAsyncFunction, [])
);
// Initial state
expect(result.current.loading).toBe(true);
expect(result.current.data).toBe(null);
expect(result.current.error).toBe(null);
// Wait for async operation to complete
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
// Final state
expect(result.current.loading).toBe(false);
expect(result.current.data).toEqual(expectedData);
expect(result.current.error).toBe(null);
});
it('should handle async operation failure', async () => {
const expectedError = new Error('Test error');
mockAsyncFunction.mockRejectedValue(expectedError);
const { result } = renderHook(() =>
useAsync(mockAsyncFunction, [])
);
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(result.current.loading).toBe(false);
expect(result.current.data).toBe(null);
expect(result.current.error).toEqual(expectedError);
});
it('should refetch data when dependencies change', async () => {
const { result, rerender } = renderHook(
({ dep }) => useAsync(() => mockAsyncFunction(dep), [dep]),
{ initialProps: { dep: 'initial' } }
);
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(mockAsyncFunction).toHaveBeenCalledWith('initial');
// Change dependency
rerender({ dep: 'updated' });
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(mockAsyncFunction).toHaveBeenCalledWith('updated');
expect(mockAsyncFunction).toHaveBeenCalledTimes(2);
});
});import React, { useState, useRef, useEffect } from 'react';
interface DropdownProps {
label: string;
options: Array<{ value: string; label: string }>;
value?: string;
onChange: (value: string) => void;
}
export const AccessibleDropdown: React.FC<DropdownProps> = ({
label,
options,
value,
onChange
}) => {
const [isOpen, setIsOpen] = useState(false);
const [activeIndex, setActiveIndex] = useState(-1);
const buttonRef = useRef<HTMLButtonElement>(null);
const listRef = useRef<HTMLUListElement>(null);
// Handle keyboard navigation
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (!isOpen) return;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
setActiveIndex(prev =>
prev < options.length - 1 ? prev + 1 : 0
);
break;
case 'ArrowUp':
e.preventDefault();
setActiveIndex(prev =>
prev > 0 ? prev - 1 : options.length - 1
);
break;
case 'Enter':
case ' ':
e.preventDefault();
if (activeIndex >= 0) {
onChange(options[activeIndex].value);
setIsOpen(false);
}
break;
case 'Escape':
setIsOpen(false);
buttonRef.current?.focus();
break;
}
};
if (isOpen) {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}
}, [isOpen, activeIndex, options, onChange]);
// Focus management
useEffect(() => {
if (isOpen && activeIndex >= 0) {
const activeOption = listRef.current?.children[activeIndex] as HTMLElement;
activeOption?.scrollIntoView({ block: 'nearest' });
}
}, [activeIndex, isOpen]);
const selectedOption = options.find(opt => opt.value === value);
return (
<div className="dropdown">
<label id="dropdown-label" className="dropdown-label">
{label}
</label>
<button
ref={buttonRef}
type="button"
className="dropdown-button"
onClick={() => setIsOpen(!isOpen)}
onBlur={(e) => {
// Close dropdown if focus moves outside
if (!e.currentTarget.contains(e.relatedTarget)) {
setIsOpen(false);
}
}}
aria-labelledby="dropdown-label"
aria-expanded={isOpen}
aria-haspopup="listbox"
>
{selectedOption?.label || 'Select an option'}
<span aria-hidden="true">▼</span>
</button>
{isOpen && (
<ul
ref={listRef}
className="dropdown-list"
role="listbox"
aria-labelledby="dropdown-label"
>
{options.map((option, index) => (
<li
key={option.value}
className={`dropdown-option ${
index === activeIndex ? 'active' : ''
} ${option.value === value ? 'selected' : ''}`}
role="option"
aria-selected={option.value === value}
onClick={() => {
onChange(option.value);
setIsOpen(false);
buttonRef.current?.focus();
}}
onMouseEnter={() => setActiveIndex(index)}
>
{option.label}
</li>
))}
</ul>
)}
</div>
);
};// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [
react({
// Enable React Refresh
fastRefresh: true,
// Configure JSX runtime
jsxRuntime: 'automatic'
})
],
// Path resolution
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@hooks': resolve(__dirname, 'src/hooks'),
'@utils': resolve(__dirname, 'src/utils'),
'@types': resolve(__dirname, 'src/types')
}
},
// Development server
server: {
port: 3000,
open: true,
cors: true
},
// Build configuration
build: {
target: 'esnext',
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
utils: ['lodash', 'date-fns']
}
}
}
},
// Environment variables
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version)
}
});// .eslintrc.json
{
"extends": [
"eslint:recommended",
"@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"plugins": [
"react",
"react-hooks",
"@typescript-eslint",
"jsx-a11y"
],
"rules": {
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-function-return-type": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"jsx-a11y/anchor-is-valid": "off"
},
"settings": {
"react": {
"version": "detect"
}
}
}This guide provides a comprehensive foundation for modern React development. Remember to:
- Use TypeScript for type safety and better developer experience
- Follow component composition patterns for reusable and maintainable code
- Optimize performance with memoization and code splitting
- Write comprehensive tests for components and hooks
- Ensure accessibility with semantic HTML and ARIA attributes
- Use modern tooling like Vite for fast development and builds
- Implement proper state management based on application complexity
Stay updated with React ecosystem changes and adopt new patterns and tools as they mature and become widely adopted by the community.