Sign In Create Account
React Components

React Components

Best practices for building maintainable React functional components with proper structure and naming.

Component Naming and File Structure

Use PascalCase for component files and follow a consistent file structure that makes components easy to find and maintain.

  • • Use PascalCase for component files: AppLayout.tsx
  • • Match component name with filename
  • • Use descriptive names that explain the component's purpose
  • • Group related components in folders
  • • Export components using named exports

Component Naming Examples

// ✅ Good component naming and structure
// File: components/layout/AppLayout.tsx
export const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
  return (
    <div className="app-layout">
      <Header />
      <main>{children}</main>
      <Footer />
    </div>
  );
};

// File: components/user/UserProfile.tsx
export const UserProfile: React.FC<UserProfileProps> = ({ user }) => {
  return (
    <div className="user-profile">
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
};

// File: components/forms/ContactForm.tsx
export const ContactForm: React.FC = () => {
  const [formData, setFormData] = useState<ContactFormData>({
    name: '',
    email: '',
    message: ''
  });

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
    </form>
  );
};

// ❌ Avoid - poor naming conventions
// File: components/layout.tsx (not descriptive)
export const Layout = () => { /* ... */ };

// File: components/form.js (not specific, wrong extension)
export const Form = () => { /* ... */ };

// File: components/userprofile.tsx (not PascalCase)
export const userprofile = () => { /* ... */ };

Functional Components Structure

Always use functional components with hooks. Follow a consistent internal structure for better readability.

  • • Use functional components exclusively
  • • Define interfaces before the component
  • • Order hooks consistently
  • • Extract complex logic into custom hooks
  • • Use TypeScript for better type safety

Component Structure Pattern

// ✅ Recommended component structure
interface UserDashboardProps {
  userId: string;
  onUserUpdate?: (user: User) => void;
}

interface User {
  id: string;
  name: string;
  email: string;
  lastLogin?: Date;
}

export const UserDashboard: React.FC<UserDashboardProps> = ({ 
  userId, 
  onUserUpdate 
}) => {
  // 1. State hooks first
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  // 2. Custom hooks
  const { updateUser } = useUserApi();

  // 3. Effect hooks
  useEffect(() => {
    fetchUserData();
  }, [userId]);

  // 4. Event handlers and other functions
  const fetchUserData = async () => {
    try {
      setIsLoading(true);
      const userData = await getUserById(userId);
      setUser(userData);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error');
    } finally {
      setIsLoading(false);
    }
  };

  const handleUserUpdate = async (updates: Partial<User>) => {
    if (!user) return;
    
    const updatedUser = await updateUser(user.id, updates);
    setUser(updatedUser);
    onUserUpdate?.(updatedUser);
  };

  // 5. Early returns for loading/error states
  if (isLoading) {
    return <LoadingSpinner />;
  }

  if (error) {
    return <ErrorMessage message={error} />;
  }

  if (!user) {
    return <EmptyState message="User not found" />;
  }

  // 6. Main render
  return (
    <div className="user-dashboard">
      <UserProfile user={user} onUpdate={handleUserUpdate} />
      <UserActivity userId={user.id} />
      <UserSettings user={user} onUpdate={handleUserUpdate} />
    </div>
  );
};

// ❌ Avoid - class components
class UserDashboard extends React.Component<UserDashboardProps> {
  // Class components are deprecated in our codebase
}

// ❌ Avoid - unstructured functional component
export const UserDashboard = ({ userId }) => {
  const handleClick = () => { /* ... */ };
  const [user, setUser] = useState();
  const [loading, setLoading] = useState(false);
  
  useEffect(() => {
    // effect logic mixed with other code
  });

  return <div>{/* ... */}</div>;
};

Props and TypeScript Interface Design

Design clear, well-typed interfaces that make component usage obvious and prevent common errors.

Props Interface Design

// ✅ Well-designed props interfaces
interface ButtonProps {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  loading?: boolean;
  onClick?: () => void;
  type?: 'button' | 'submit' | 'reset';
  className?: string;
}

export const Button: React.FC<ButtonProps> = ({
  children,
  variant = 'primary',
  size = 'medium',
  disabled = false,
  loading = false,
  onClick,
  type = 'button',
  className = ''
}) => {
  return (
    <button
      type={type}
      disabled={disabled || loading}
      onClick={onClick}
      className={`btn btn-${variant} btn-${size} ${className}`}
    >
      {loading ? <Spinner /> : children}
    </button>
  );
};

// ✅ Complex component with optional sections
interface ProductCardProps {
  product: Product;
  showPrice?: boolean;
  showDescription?: boolean;
  showActions?: boolean;
  onAddToCart?: (product: Product) => void;
  onViewDetails?: (productId: string) => void;
}

export const ProductCard: React.FC<ProductCardProps> = ({
  product,
  showPrice = true,
  showDescription = true,
  showActions = true,
  onAddToCart,
  onViewDetails
}) => {
  return (
    <div className="product-card">
      <img src={product.imageUrl} alt={product.name} />
      <h3>{product.name}</h3>
      
      {showDescription && product.description && (
        <p>{product.description}</p>
      )}
      
      {showPrice && (
        <span className="price">${product.price}</span>
      )}
      
      {showActions && (
        <div className="actions">
          <Button onClick={() => onViewDetails?.(product.id)}>
            View Details
          </Button>
          <Button 
            variant="primary"
            onClick={() => onAddToCart?.(product)}
          >
            Add to Cart
          </Button>
        </div>
      )}
    </div>
  );
};

// ✅ Generic component with flexible children
interface ModalProps {
  isOpen: boolean;
  onClose: () => void;
  title?: string;
  size?: 'small' | 'medium' | 'large';
  children: React.ReactNode;
}

export const Modal: React.FC<ModalProps> = ({
  isOpen,
  onClose,
  title,
  size = 'medium',
  children
}) => {
  if (!isOpen) return null;

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div 
        className={`modal modal-${size}`}
        onClick={(e) => e.stopPropagation()}
      >
        {title && (
          <header className="modal-header">
            <h2>{title}</h2>
            <button onClick={onClose}>×</button>
          </header>
        )}
        <div className="modal-body">
          {children}
        </div>
      </div>
    </div>
  );
};

// ❌ Avoid - poorly typed props
interface BadProps {
  data: any; // Too generic
  callback: Function; // Untyped function
  config: object; // Generic object
}

// ❌ Avoid - missing default values
export const BadButton = ({ variant, size, children }: any) => {
  // No defaults, will cause undefined behavior
  return <button className={`btn-${variant}-${size}`}>{children}</button>;
};

Component Composition and Reusability

Build components that are easy to compose and reuse across your application.

Component Composition Patterns

// ✅ Composable layout components
interface CardProps {
  children: React.ReactNode;
  className?: string;
}

export const Card: React.FC<CardProps> = ({ children, className = '' }) => {
  return (
    <div className={`card ${className}`}>
      {children}
    </div>
  );
};

export const CardHeader: React.FC<CardProps> = ({ children, className = '' }) => {
  return (
    <div className={`card-header ${className}`}>
      {children}
    </div>
  );
};

export const CardBody: React.FC<CardProps> = ({ children, className = '' }) => {
  return (
    <div className={`card-body ${className}`}>
      {children}
    </div>
  );
};

export const CardFooter: React.FC<CardProps> = ({ children, className = '' }) => {
  return (
    <div className={`card-footer ${className}`}>
      {children}
    </div>
  );
};

// Usage - flexible composition
const UserProfileCard = ({ user }: { user: User }) => {
  return (
    <Card>
      <CardHeader>
        <h2>{user.name}</h2>
      </CardHeader>
      <CardBody>
        <p>Email: {user.email}</p>
        <p>Role: {user.role}</p>
      </CardBody>
      <CardFooter>
        <Button onClick={handleEdit}>Edit Profile</Button>
      </CardFooter>
    </Card>
  );
};

// ✅ Higher-order component for data fetching
interface WithLoadingProps<T> {
  data: T | null;
  isLoading: boolean;
  error: string | null;
  children: (data: T) => React.ReactNode;
  loadingComponent?: React.ReactNode;
  errorComponent?: (error: string) => React.ReactNode;
}

export const WithLoading = <T,>({
  data,
  isLoading,
  error,
  children,
  loadingComponent = <LoadingSpinner />,
  errorComponent = (err: string) => <ErrorMessage message={err} />
}: WithLoadingProps<T>) => {
  if (isLoading) return <>{loadingComponent}</>;
  if (error) return <>{errorComponent(error)}</>;
  if (!data) return <EmptyState />;
  
  return <>{children(data)}</>;
};

// Usage with data fetching
const UserList = () => {
  const { users, isLoading, error } = useUsers();

  return (
    <WithLoading
      data={users}
      isLoading={isLoading}
      error={error}
      loadingComponent={<div>Loading users...</div>}
    >
      {(users) => (
        <div>
          {users.map(user => (
            <UserCard key={user.id} user={user} />
          ))}
        </div>
      )}
    </WithLoading>
  );
};

// ✅ Render prop pattern for flexible UI
interface DataListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  renderEmpty?: () => React.ReactNode;
  className?: string;
}

export const DataList = <T,>({
  items,
  renderItem,
  renderEmpty = () => <p>No items found</p>,
  className = ''
}: DataListProps<T>) => {
  if (items.length === 0) {
    return <div className={className}>{renderEmpty()}</div>;
  }

  return (
    <div className={`data-list ${className}`}>
      {items.map((item, index) => (
        <div key={index} className="data-list-item">
          {renderItem(item, index)}
        </div>
      ))}
    </div>
  );
};

// Usage - flexible rendering
const ProductList = ({ products }: { products: Product[] }) => {
  return (
    <DataList
      items={products}
      renderItem={(product) => (
        <ProductCard 
          product={product} 
          onAddToCart={handleAddToCart}
        />
      )}
      renderEmpty={() => (
        <EmptyState 
          title="No products found"
          action={<Button onClick={handleRefresh}>Refresh</Button>}
        />
      )}
    />
  );
};