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.