Skip to main content

Component Library Implementation Guide

This guide provides practical instructions for implementing components in the BuildAppsWith component library, including best practices, code samples, and troubleshooting tips.

Implementation Process

1. Component Planning

Before writing any code, follow these planning steps:
  1. Review Requirements: Understand the component’s purpose, variants, and usage
  2. Check Existing Components: Verify if the component already exists or can be composed from existing components
  3. Identify Dependencies: Determine what external libraries or internal utilities are needed
  4. Define Props Interface: Define the TypeScript interface for component props
// Example Props Interface
interface ButtonProps {
  /** Button contents */
  children: React.ReactNode;
  /** The visual variant of the button */
  variant?: 'default' | 'outline' | 'ghost' | 'destructive';
  /** Button size */
  size?: 'sm' | 'md' | 'lg';
  /** Optional icon to display before the button text */
  icon?: React.ReactNode;
  /** Whether the button is disabled */
  disabled?: boolean;
  /** Button type attribute */
  type?: 'button' | 'submit' | 'reset';
  /** Additional CSS classes */
  className?: string;
  /** Event handler for click event */
  onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
}

2. Component Implementation

Follow these steps when implementing a new component:
  1. Create Component Directory: Follow the domain-based organization pattern
  2. Write Component Code: Implement the component using TypeScript and React
  3. Implement Variants: Add support for all required variants
  4. Add Documentation: Include JSDoc comments for the component and props
  5. Create Tests: Write comprehensive tests for the component
// Example Component Implementation
import React from 'react';
import { cn } from '@/lib/utils';

/**
 * Button component for user actions
 * 
 * @example
 * <Button variant="outline" size="lg">Click Me</Button>
 */
export function Button({
  children,
  variant = 'default',
  size = 'md',
  icon,
  disabled = false,
  type = 'button',
  className,
  onClick,
  ...props
}: ButtonProps) {
  // Size classes mapping
  const sizeClasses = {
    sm: 'px-3 py-1 text-sm',
    md: 'px-4 py-2',
    lg: 'px-6 py-3 text-lg',
  };
  
  // Variant classes mapping
  const variantClasses = {
    default: 'bg-primary text-white hover:bg-primary-dark',
    outline: 'border border-primary text-primary hover:bg-primary/10',
    ghost: 'text-primary hover:bg-primary/10',
    destructive: 'bg-red-600 text-white hover:bg-red-700',
  };
  
  return (
    <button
      type={type}
      disabled={disabled}
      className={cn(
        'inline-flex items-center justify-center rounded-md font-medium transition-colors',
        'focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary',
        'disabled:opacity-50 disabled:pointer-events-none',
        sizeClasses[size],
        variantClasses[variant],
        className
      )}
      onClick={onClick}
      {...props}
    >
      {icon && <span className="mr-2">{icon}</span>}
      {children}
    </button>
  );
}

3. Component Testing

All components should include comprehensive tests:
// Example Component Test
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('renders correctly', () => {
    render(<Button>Click Me</Button>);
    expect(screen.getByRole('button', { name: 'Click Me' })).toBeInTheDocument();
  });
  
  it('handles click events', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click Me</Button>);
    
    fireEvent.click(screen.getByRole('button', { name: 'Click Me' }));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
  
  it('renders different variants', () => {
    const { rerender } = render(<Button variant="default">Default</Button>);
    expect(screen.getByRole('button')).toHaveClass('bg-primary');
    
    rerender(<Button variant="outline">Outline</Button>);
    expect(screen.getByRole('button')).toHaveClass('border-primary');
  });
  
  it('disables the button when disabled', () => {
    render(<Button disabled>Disabled</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
  });
});

4. Component Documentation

Document your component using JSDoc comments and provide usage examples:
/**
 * Avatar component for displaying user profile images
 *
 * @example
 * // With image
 * <Avatar src="/path/to/image.jpg" alt="User Name" />
 *
 * @example
 * // With fallback initials
 * <Avatar name="John Doe" />
 */
export function Avatar({
  src,
  alt,
  name,
  size = 'md',
  className,
}: AvatarProps) {
  // Implementation...
}

Integration with Magic UI Pro

Installing Magic UI Components

To install a Magic UI Pro component:
# Using shadcn/ui pattern
pnpm dlx shadcn@latest add "https://magicui.design/r/button.json"

Extending Magic UI Components

To extend a Magic UI Pro component:
// Import the base component
import { Button as MagicButton } from '@/components/ui/core/button';

// Extend with custom functionality
export function Button(props: ButtonProps) {
  // Add analytics tracking
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    trackButtonClick(props.analyticsId);
    props.onClick?.(e);
  };
  
  return <MagicButton {...props} onClick={handleClick} />;
}

Common Implementation Patterns

Container/Presenter Pattern

Separate logic from presentation using the container/presenter pattern:
// Presenter component - pure rendering
function UserProfileCard({
  user,
  isLoading,
  error,
  onEdit,
}: UserProfileCardProps) {
  if (isLoading) return <UserProfileSkeleton />;
  if (error) return <ErrorMessage message={error} />;
  
  return (
    <Card>
      <CardHeader>
        <Avatar src={user.avatarUrl} name={user.name} />
        <h3 className="text-lg font-medium">{user.name}</h3>
      </CardHeader>
      <CardContent>{/* User details */}</CardContent>
      <CardFooter>
        <Button onClick={onEdit}>Edit Profile</Button>
      </CardFooter>
    </Card>
  );
}

// Container component - handles data and logic
function UserProfileContainer({ userId }: { userId: string }) {
  const { data: user, isLoading, error } = useUser(userId);
  const { openModal } = useModal();
  
  const handleEdit = () => {
    openModal('editProfile', { userId });
  };
  
  return (
    <UserProfileCard
      user={user}
      isLoading={isLoading}
      error={error}
      onEdit={handleEdit}
    />
  );
}

Compound Components

Use compound components for complex UI elements:
// Compound component pattern
import { createContext, useContext } from 'react';

const TabsContext = createContext<TabsContextValue | undefined>(undefined);

function Tabs({ children, value, onValueChange }: TabsProps) {
  return (
    <TabsContext.Provider value={{ value, onValueChange }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

function TabsList({ children }: { children: React.ReactNode }) {
  return <div className="tabs-list">{children}</div>;
}

function TabsTrigger({ value, children }: TabsTriggerProps) {
  const context = useContext(TabsContext);
  if (!context) throw new Error('TabsTrigger must be used within Tabs');
  
  const isActive = context.value === value;
  
  return (
    <button
      className={`tabs-trigger ${isActive ? 'active' : ''}`}
      onClick={() => context.onValueChange(value)}
      aria-selected={isActive}
      role="tab"
    >
      {children}
    </button>
  );
}

function TabsContent({ value, children }: TabsContentProps) {
  const context = useContext(TabsContext);
  if (!context) throw new Error('TabsContent must be used within Tabs');
  
  return context.value === value ? (
    <div className="tabs-content" role="tabpanel">
      {children}
    </div>
  ) : null;
}

// Export as combined object
export const TabsComponent = {
  Root: Tabs,
  List: TabsList,
  Trigger: TabsTrigger,
  Content: TabsContent,
};

// Usage:
function Example() {
  return (
    <TabsComponent.Root value="tab1" onValueChange={setValue}>
      <TabsComponent.List>
        <TabsComponent.Trigger value="tab1">Tab 1</TabsComponent.Trigger>
        <TabsComponent.Trigger value="tab2">Tab 2</TabsComponent.Trigger>
      </TabsComponent.List>
      <TabsComponent.Content value="tab1">Content 1</TabsComponent.Content>
      <TabsComponent.Content value="tab2">Content 2</TabsComponent.Content>
    </TabsComponent.Root>
  );
}

Hooks for Logic

Extract complex logic into custom hooks:
// Custom hook for form validation
function useFormValidation(schema) {
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  
  const validate = useCallback((values) => {
    try {
      schema.parse(values);
      setErrors({});
      return true;
    } catch (error) {
      const formattedErrors = formatZodErrors(error);
      setErrors(formattedErrors);
      return false;
    }
  }, [schema]);
  
  const handleBlur = useCallback((field) => {
    setTouched((prev) => ({ ...prev, [field]: true }));
  }, []);
  
  return { errors, touched, validate, handleBlur };
}

// Usage in component
function RegistrationForm() {
  const [values, setValues] = useState({ email: '', password: '' });
  const { errors, touched, validate, handleBlur } = useFormValidation(userSchema);
  
  const handleSubmit = (e) => {
    e.preventDefault();
    if (validate(values)) {
      // Submit the form
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      {/* Form fields */}
    </form>
  );
}

Performance Optimization

Memoization

Use React.memo for components that render often but rarely change:
// Memoized component
const ExpensiveComponent = React.memo(function ExpensiveComponent({
  data,
  onItemClick,
}: Props) {
  return (
    <div>
      {data.map((item) => (
        <div key={item.id} onClick={() => onItemClick(item.id)}>
          {item.name}
        </div>
      ))}
    </div>
  );
});

// Stable callback with useCallback
function ParentComponent() {
  const [items, setItems] = useState([]);
  
  // Memoize callback to prevent unnecessary re-renders
  const handleItemClick = useCallback((id) => {
    console.log(`Item clicked: ${id}`);
  }, []);
  
  return <ExpensiveComponent data={items} onItemClick={handleItemClick} />;
}

Code Splitting

Use dynamic imports to split large components:
// Code splitting with dynamic import
import dynamic from 'next/dynamic';

// Dynamically load the chart component
const Chart = dynamic(() => import('@/components/chart'), {
  loading: () => <div>Loading chart...</div>,
  ssr: false, // Disable server-side rendering if needed
});

function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Chart data={data} />
    </div>
  );
}

Troubleshooting

Common Issues and Solutions

IssueCauseSolution
Component re-renders too oftenUnstable props or context valuesUse useCallback, useMemo, or React.memo
Type errors with propsIncorrect TypeScript typesDefine explicit interfaces and use proper union types
CSS conflicts with TailwindSpecificity issues or incorrect class orderUse the cn() utility to merge classes properly
Layout shifts during loadingMissing size constraintsSet explicit width/height or aspect ratios
Z-index issues with overlaysIncorrect stacking contextUse consistent z-index scale and ensure proper DOM nesting

Debugging Techniques

  1. Component Boundaries: Add outline to see component boundaries
    [data-debug="true"] * {
      outline: 1px solid rgba(255, 0, 0, 0.2);
    }
    
  2. Render Counting: Track component renders
    function useRenderCount() {
      const count = useRef(0);
      count.current++;
      console.log(`Component rendered ${count.current} times`);
    }
    
  3. Prop Debugging: Log props changes
    function usePrevious(value) {
      const ref = useRef();
      useEffect(() => {
        ref.current = value;
      }, [value]);
      return ref.current;
    }
    
    function Component(props) {
      const prevProps = usePrevious(props);
      useEffect(() => {
        console.log('Props changed:', {
          previous: prevProps,
          current: props,
        });
      }, [props, prevProps]);
    }
    

Best Practices

Do’s

  • ✅ Use TypeScript for all components
  • ✅ Write comprehensive tests
  • ✅ Follow accessibility guidelines
  • ✅ Document props with JSDoc
  • ✅ Consider all error states
  • ✅ Use React DevTools for debugging
  • ✅ Validate props with PropTypes or TypeScript

Don’ts

  • ❌ Mutate props or state directly
  • ❌ Use any type in TypeScript
  • ❌ Create unnecessarily complex components
  • ❌ Put business logic in UI components
  • ❌ Ignore accessibility
  • ❌ Use inline styles (use Tailwind instead)

See Also