React Best Practices for Enterprise Applications
Building React applications for enterprise requires pendekatan yang berbeda dari project kecil. Artikel ini membahas best practices yang telah teruji dalam pengembangan aplikasi enterprise-scale.
1. Project Structure
Struktur folder yang terorganisir sangat penting untuk maintainability:
src/
├── components/
│ ├── ui/ # Reusable UI components
│ ├── forms/ # Form-specific components
│ └── layout/ # Layout components
├── features/ # Feature-based modules
│ ├── auth/
│ ├── dashboard/
│ └── users/
├── hooks/ # Custom React hooks
├── services/ # API services
├── utils/ # Utility functions
├── types/ # TypeScript types
└── stores/ # State management
2. Component Design
Single Responsibility Principle
Setiap component seharusnya hanya melakukan satu hal:
// Bad: Component doing too much
function UserDashboard() {
// Fetch data
// Transform data
// Render UI
// Handle forms
// ...
}
// Good: Separated concerns
function UserDashboard() {
const { data } = useUsers();
return <UserList users={data} />;
}
function UserList({ users }) {
return (
<ul>
{users.map(user => <UserCard key={user.id} user={user} />)}
</ul>
);
}
Composition over Inheritance
Gunakan composition untuk reusability:
// Card Component
function Card({ children, title, actions }) {
return (
<div className="card">
<div className="card-header">
<h3>{title}</h3>
{actions && <div className="card-actions">{actions}</div>}
</div>
<div className="card-content">{children}</div>
</div>
);
}
// Usage
<Card title="User Profile" actions={<EditButton />}>
<UserDetails user={user} />
</Card>
3. State Management
Local vs Global State
// Local State - useState for component-specific data
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// Global State - Context or State Management Library
const AuthContext = createContext();
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = async (credentials) => {
const user = await authService.login(credentials);
setUser(user);
};
return (
<AuthContext.Provider value={{ user, login }}>
{children}
</AuthContext.Provider>
);
}
State Normalization
Untuk complex data, gunakan normalized state:
// Instead of nested array
const state = {
users: [
{ id: 1, name: 'John', posts: [{ id: 1, title: 'Post 1' }] }
]
};
// Use normalized structure
const state = {
users: {
byId: { 1: { id: 1, name: 'John', posts: [1] } },
allIds: [1]
},
posts: {
byId: { 1: { id: 1, title: 'Post 1', userId: 1 } },
allIds: [1]
}
};
4. Performance Optimization
Memoization
Gunakan memoization dengan bijak:
import { memo, useMemo, useCallback } from 'react';
// Memoize expensive component
const ExpensiveList = memo(function ExpensiveList({ items }) {
return (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
});
// Memoize expensive calculations
function Dashboard({ data }) {
const processedData = useMemo(() => {
return data.map(item => expensiveOperation(item));
}, [data]);
// Memoize callbacks
const handleClick = useCallback((id) => {
console.log('Clicked:', id);
}, []);
return <ExpensiveList items={processedData} onClick={handleClick} />;
}
Code Splitting
Split code untuk reduce initial load:
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<HeavyComponent />
</Suspense>
);
}
5. Testing Strategy
Unit Tests
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
describe('Counter', () => {
it('increments count when clicked', async () => {
render(<Counter />);
const button = screen.getByRole('button');
expect(button).toHaveTextContent('0');
await userEvent.click(button);
expect(button).toHaveTextContent('1');
});
});
Integration Tests
describe('User Dashboard', () => {
it('displays user data after loading', async () => {
render(<UserDashboard />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
});
});
6. Error Handling
Error Boundaries
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <ErrorFallback />;
}
return this.props.children;
}
}
// Usage
<ErrorBoundary>
<RiskyComponent />
</ErrorBoundary>
7. TypeScript Integration
Gunakan TypeScript untuk type safety:
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user' | 'guest';
}
interface UserCardProps {
user: User;
onEdit: (userId: string) => void;
isLoading?: boolean;
}
function UserCard({ user, onEdit, isLoading = false }: UserCardProps) {
// Component implementation
}
Kesimpulan
Enterprise React applications membutuhkan:
- Struktur yang terorganisir untuk maintainability
- Performance optimization untuk user experience
- Comprehensive testing untuk reliability
- Type safety untuk fewer bugs
- Proper error handling untuk resilience
Implementasikan practices ini secara bertahap dan sesuaikan dengan kebutuhan team Anda.