Aller au contenu principal

Baldr Dashboard - Component Patterns Guide

Version: 1.0.0
Last Updated: October 28, 2025
Maintainer: Development Team


Table of Contents

  1. Introduction
  2. Atomic Design System
  3. Component Hierarchy
  4. Naming Conventions
  5. File Structure
  6. Atoms
  7. Molecules
  8. Organisms
  9. Templates
  10. When to Create New Components
  11. Component Composition
  12. Styling Patterns
  13. Props Patterns
  14. State Management
  15. Performance Optimization
  16. Testing Patterns
  17. Accessibility Patterns
  18. Best Practices
  19. Anti-Patterns to Avoid
  20. Migration Guide

Introduction

This guide documents the component architecture patterns used in the Baldr Dashboard. We follow Atomic Design principles to create a scalable, maintainable component system.

Why Atomic Design?

  • Consistency: Reusable components ensure UI consistency
  • Scalability: Easy to add new features by composing existing components
  • Maintainability: Changes propagate automatically through composition
  • Testability: Smaller components are easier to test
  • Documentation: Clear hierarchy makes onboarding easier

Key Principles

  1. Single Responsibility: Each component does one thing well
  2. Composition over Inheritance: Build complex UIs by composing simple components
  3. Props over State: Prefer controlled components
  4. Styling Consistency: Use Tailwind CSS and design tokens
  5. Accessibility First: ARIA attributes and keyboard navigation

Atomic Design System

Baldr Dashboard implements Atomic Design with four levels:

Atoms → Molecules → Organisms → Templates

Visual Hierarchy

graph TD
A[Atoms] --> M[Molecules]
M --> O[Organisms]
O --> T[Templates]
T --> P[Pages]

A1[Button] --> M1[LanguageSelector]
A2[Input] --> M2[InputField]
M1 --> O1[Menu]
M2 --> O2[TableManager]
O1 --> T1[DashboardTemplate]
O2 --> T1
T1 --> P1[ModulesPage]

Component Hierarchy

Atoms (Level 1)

Definition: The smallest, indivisible UI elements.

Characteristics:

  • Cannot be broken down further
  • Highly reusable
  • No business logic
  • Style-focused
  • Pure presentation

Examples:

  • button.atom.tsx - Button with variants
  • input.atom.tsx - Text input field
  • link.atom.tsx - Navigation link
  • paragraph.atom.tsx - Text paragraph
  • checkbox.atom.tsx - Checkbox input

Molecules (Level 2)

Definition: Groups of atoms functioning together as a unit.

Characteristics:

  • Composed of 2-5 atoms
  • Single purpose/responsibility
  • Reusable in multiple contexts
  • May have simple internal state
  • No complex business logic

Examples:

  • inputField.molecule.tsx - Input + Label + Error
  • languageSelector.molecule.tsx - Dropdown + Icons
  • checkboxField.molecule.tsx - Checkbox + Label
  • translationTabs.molecule.tsx - TabView + Language logic

Organisms (Level 3)

Definition: Complex UI components composed of molecules and atoms.

Characteristics:

  • Composed of multiple molecules/atoms
  • May contain business logic
  • Context-specific functionality
  • Can be reusable or page-specific
  • May interact with APIs/context

Examples:

  • tableManager.organism.tsx - Complex table with actions
  • menu.organism.tsx - Navigation with modules
  • Form containers with validation
  • Data display panels with filtering

Templates (Level 4)

Definition: Page-level layouts defining content structure.

Characteristics:

  • Define page layout
  • Compose organisms
  • No specific data/content
  • Reusable across pages
  • Handle responsive layouts

Examples:

  • dashboardTemplate.template.tsx - Main dashboard layout
  • authTemplate.template.tsx - Authentication pages layout
  • contentTemplate.template.tsx - Content pages layout

Naming Conventions

File Naming

All component files follow this pattern:

<componentName>.<level>.tsx

Examples:

  • button.atom.tsx
  • inputField.molecule.tsx
  • tableManager.organism.tsx
  • dashboardTemplate.template.tsx

Component Naming

Use PascalCase for component names:

// ✅ Good
export default function Button({ ... }) { }
export default function InputField({ ... }) { }
export default function TableManager({ ... }) { }

// ❌ Bad
export default function button({ ... }) { }
export default function input_field({ ... }) { }
export default function table_manager({ ... }) { }

Props Interface Naming

// For exported interfaces
export interface ButtonProps {
variant: string;
onClick: () => void;
}

// For internal interfaces
interface InternalState {
isOpen: boolean;
}

File Structure

Directory Organization

app/components/
├── atoms/ # Level 1: Atoms
│ ├── button.atom.tsx
│ ├── input.atom.tsx
│ ├── link.atom.tsx
│ └── ...
├── molecules/ # Level 2: Molecules
│ ├── inputField.molecule.tsx
│ ├── languageSelector.molecule.tsx
│ └── ...
├── organisms/ # Level 3: Organisms
│ ├── tableManager.organism.tsx
│ ├── menu.organism.tsx
│ └── ...
└── template/ # Level 4: Templates
├── dashboardTemplate.template.tsx
└── ...

Component File Structure

// 1. Imports (grouped and ordered)
import React, { useState, useEffect } from 'react';
import { PrimeReactComponent } from 'primereact/component';
import clsx from 'clsx';

// Internal imports
import SubComponent from './subComponent.atom';
import { useCustomHook } from '~/hooks/useCustomHook';

// 2. Types and Interfaces
interface ComponentProps {
// Props definition
}

// 3. Component Documentation (JSDoc)
/**
* Component: ComponentName
* Description and usage
*/

// 4. Component Implementation
export default function ComponentName({ props }: ComponentProps) {
// State
const [state, setState] = useState();

// Effects
useEffect(() => {
// Effect logic
}, []);

// Handlers
const handleClick = () => {
// Handler logic
};

// Render
return (
<div>
{/* JSX */}
</div>
);
}

Atoms

When to Create an Atom

Create an atom when you need:

  • A basic UI primitive (button, input, link)
  • A wrapper for a third-party component (PrimeReact, etc.)
  • Consistent styling across the application
  • A reusable visual element

Atom Characteristics

/**
* ✅ Good Atom Example
* - Single purpose (render a button)
* - Style variants via props
* - No business logic
* - Highly reusable
*/
export default function Button({
variant = 'primary',
size = 'default',
children,
onClick,
disabled = false,
}: ButtonProps) {
const variantClasses = {
primary: 'bg-primary-700 text-white',
secondary: 'bg-gray-200 text-gray-900',
danger: 'bg-red-600 text-white',
}[variant];

return (
<button
onClick={onClick}
disabled={disabled}
className={clsx(
'px-4 py-2 rounded-md transition-colors',
variantClasses,
disabled && 'opacity-50 cursor-not-allowed'
)}
>
{children}
</button>
);
}

Common Atom Patterns

Variant System

// Define variants as union types
type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'danger';
type ButtonSize = 'sm' | 'md' | 'lg';

interface ButtonProps {
variant?: ButtonVariant;
size?: ButtonSize;
}

// Map variants to CSS classes
const variantClasses: Record<ButtonVariant, string> = {
primary: 'bg-primary-700 text-white hover:bg-primary-800',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
tertiary: 'bg-transparent text-primary-700 hover:bg-primary-50',
danger: 'bg-red-600 text-white hover:bg-red-700',
};

forwardRef for Form Inputs

import { forwardRef } from 'react';

const Input = forwardRef<HTMLInputElement, InputProps>(
({ invalid, ...props }, ref) => {
return (
<input
ref={ref}
className={clsx(
'border rounded-md px-3 py-2',
invalid ? 'border-red-500' : 'border-gray-300'
)}
{...props}
/>
);
}
);

Input.displayName = 'Input';
export default Input;

Atom Best Practices

Do:

  • Keep atoms simple and focused
  • Use TypeScript for props
  • Provide default values for optional props
  • Use clsx for conditional classes
  • Support disabled and error states
  • Add ARIA attributes for accessibility

Don't:

  • Add business logic to atoms
  • Make API calls
  • Use context or global state
  • Compose other complex components
  • Handle complex user workflows

Molecules

When to Create a Molecule

Create a molecule when you need:

  • A group of atoms working together
  • Reusable form fields (input + label + error)
  • Simple interactive components
  • Components used in multiple organisms

Molecule Characteristics

/**
* ✅ Good Molecule Example
* - Combines atoms (Input + Label + Error)
* - Single responsibility (text input field)
* - Reusable across forms
* - Handles validation display
*/
export default function InputField({
label,
name,
error,
required,
...inputProps
}: InputFieldProps) {
const id = `input-${name}`;

return (
<div className="mb-4">
{/* Label atom */}
<label
htmlFor={id}
className="block text-sm font-medium text-gray-700 mb-1"
>
{label}
{required && <span className="text-red-500 ml-1">*</span>}
</label>

{/* Input atom */}
<Input
id={id}
name={name}
invalid={!!error}
{...inputProps}
/>

{/* Error paragraph atom */}
{error && (
<Paragraph size="sm" error>
{error}
</Paragraph>
)}
</div>
);
}

Common Molecule Patterns

Form Field Pattern

/**
* Standard form field molecule structure
*/
interface FormFieldProps {
label: string;
name: string;
error?: string;
required?: boolean;
helpText?: string;
// Input-specific props
[key: string]: any;
}

export default function FormField({
label,
name,
error,
required,
helpText,
...inputProps
}: FormFieldProps) {
return (
<div className="form-field">
<label htmlFor={name}>
{label}
{required && <span className="required">*</span>}
</label>

<Input id={name} name={name} {...inputProps} />

{helpText && <Paragraph size="sm">{helpText}</Paragraph>}
{error && <Paragraph size="sm" error>{error}</Paragraph>}
</div>
);
}

Selector with Icons Pattern

/**
* Molecule combining dropdown with icon display
*/
export default function LanguageSelector({
value,
onChange,
languages,
}: LanguageSelectorProps) {
return (
<div className="flex items-center gap-2">
{/* Icon atom */}
<img
src={getLanguageIcon(value)}
alt={getLanguageIconAlt(value)}
className="w-6 h-6"
/>

{/* Dropdown atom */}
<Dropdown
options={languages}
value={value}
onChange={onChange}
/>
</div>
);
}

Molecule Best Practices

Do:

  • Compose 2-5 atoms maximum
  • Keep single responsibility
  • Make reusable across contexts
  • Handle simple internal state
  • Provide clear prop interfaces

Don't:

  • Add complex business logic
  • Make API calls directly
  • Compose other molecules (rarely)
  • Create page-specific molecules
  • Handle routing or navigation

Organisms

When to Create an Organism

Create an organism when you need:

  • Complex UI sections (tables, forms, menus)
  • Components with business logic
  • Features specific to your application
  • Components that interact with APIs/context

Organism Characteristics

/**
* ✅ Good Organism Example
* - Composes multiple molecules and atoms
* - Contains business logic
* - Interacts with context
* - Handles complex user workflows
*/
export default function TableManager<T extends HasId>({
data,
columns,
actions,
mode,
}: TableManagerProps<T>) {
// Context usage
const { user } = useUserContext();

// Complex state
const [selection, setSelection] = useState<Record<string, boolean>>({});
const [currentMode, setCurrentMode] = useState(mode);

// Business logic
const handleBulkAction = (action: string) => {
const selectedIds = Object.keys(selection).filter(
(id) => selection[id]
);
// Perform bulk action
};

// Compose molecules and atoms
return (
<div>
{/* Action buttons molecule */}
<ActionButtons actions={actions} />

{/* Data table atom */}
<DataTable
data={data}
columns={columns}
rowSelection={selection}
onRowSelectionChange={setSelection}
/>

{/* Paginator atom */}
<Paginator {...paginationProps} />
</div>
);
}

Common Organism Patterns

Table with Actions Pattern

/**
* Comprehensive table organism with:
* - Data display
* - Row selection
* - Bulk actions
* - Pagination
* - Search/filtering
*/
export default function TableManager<T>({
data,
columns,
searchable,
paginated,
actions,
}: TableManagerProps<T>) {
// State management
const [search, setSearch] = useState('');
const [selection, setSelection] = useState({});
const [pagination, setPagination] = useState({ first: 0, rows: 12 });

// Data processing
const filteredData = useMemo(() => {
return data.filter(item =>
JSON.stringify(item).toLowerCase().includes(search.toLowerCase())
);
}, [data, search]);

return (
<Card>
{/* Search molecule */}
{searchable && (
<SearchBar value={search} onChange={setSearch} />
)}

{/* Action buttons */}
<ActionBar
actions={actions}
selection={selection}
/>

{/* Data table */}
<DataTable
data={filteredData}
columns={columns}
rowSelection={selection}
onRowSelectionChange={setSelection}
/>

{/* Pagination */}
{paginated && (
<Paginator
first={pagination.first}
rows={pagination.rows}
totalRecords={filteredData.length}
onPageChange={(e) => setPagination(e)}
/>
)}
</Card>
);
}
/**
* Navigation menu organism with:
* - Dynamic module loading
* - Responsive behavior
* - State persistence
* - Context integration
*/
export default function Menu() {
// Context
const { modules } = useModulesContext();
const { stats } = useStatsContext();

// State with persistence
const [collapsed, setCollapsed] = useCookie('menu-collapsed', false);
const [locked, setLocked] = useCookie('menu-locked', false);

// Responsive behavior
useEffect(() => {
const handleResize = () => {
if (window.innerWidth < 1024) {
setCollapsed(true);
}
};

window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);

return (
<nav className={clsx(
'menu',
collapsed && 'menu-collapsed',
locked && 'menu-locked'
)}>
{modules.map((module) => (
<MenuItem
key={module.id}
module={module}
badge={stats[module.id]}
/>
))}
</nav>
);
}

Organism Best Practices

Do:

  • Compose molecules and atoms
  • Add business logic when needed
  • Use context for global state
  • Handle complex user interactions
  • Implement error boundaries
  • Add loading states

Don't:

  • Make organisms too generic (use molecules instead)
  • Create deeply nested hierarchies
  • Mix presentation and business logic carelessly
  • Forget to memoize expensive computations

Templates

When to Create a Template

Create a template when you need:

  • Consistent page layouts
  • Reusable page structures
  • Responsive grid systems
  • Navigation wrappers

Template Characteristics

/**
* ✅ Good Template Example
* - Defines page structure
* - No specific content/data
* - Responsive layout
* - Composes organisms
*/
export default function DashboardTemplate({
children,
}: DashboardTemplateProps) {
return (
<div className="dashboard-layout">
{/* Menu organism */}
<Menu />

{/* Main content area */}
<main className="main-content">
{/* Breadcrumbs organism */}
<Breadcrumbs />

{/* Page content (children) */}
<div className="page-content">
{children}
</div>
</main>
</div>
);
}

Common Template Patterns

Dashboard Layout

export default function DashboardTemplate({ children }: PropsWithChildren) {
return (
<div className="min-h-screen flex">
{/* Sidebar */}
<aside className="w-64 bg-gray-50">
<Menu />
</aside>

{/* Main content */}
<div className="flex-1 flex flex-col">
{/* Header */}
<header className="h-16 bg-white shadow-sm">
<Breadcrumbs />
</header>

{/* Content */}
<main className="flex-1 p-6">
{children}
</main>
</div>
</div>
);
}

When to Create New Components

Decision Tree

graph TD
A[Need a new component?] --> B{Exists already?}
B -->|Yes| C[Use existing component]
B -->|No| D{Simple UI element?}
D -->|Yes| E[Create ATOM]
D -->|No| F{Group of atoms?}
F -->|Yes| G[Create MOLECULE]
F -->|No| H{Complex feature?}
H -->|Yes| I[Create ORGANISM]
H -->|No| J{Page layout?}
J -->|Yes| K[Create TEMPLATE]
J -->|No| L[Refactor existing components]

Questions to Ask

  1. Does this component already exist?

    • Search app/components/ before creating
    • Check if existing component can be extended
  2. Can it be broken down further?

    • If yes → Create smaller atoms/molecules first
    • If no → It's probably an atom
  3. Is it reusable?

    • Yes → Make it generic, add to atoms/molecules
    • No → Consider making it an organism or page-specific component
  4. Does it contain business logic?

    • Yes → Organism or higher
    • No → Atom or molecule
  5. Does it need context or API data?

    • Yes → Organism
    • No → Molecule or atom

Component Composition

Composition Strategies

Props Spreading

// Forward all props to child component
<Input {...inputProps} />

// Spread with overrides
<Input {...inputProps} className={clsx(inputProps.className, 'custom-class')} />

Render Props

interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}

export default function List<T>({ items, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{renderItem(item)}</li>
))}
</ul>
);
}

// Usage
<List
items={users}
renderItem={(user) => <UserCard user={user} />}
/>

Children as Function

interface ContainerProps {
children: (data: Data) => React.ReactNode;
}

export default function DataContainer({ children }: ContainerProps) {
const data = useFetchData();

if (data.isLoading) return <Loader />;
if (data.error) return <Error error={data.error} />;

return <>{children(data)}</>;
}

// Usage
<DataContainer>
{(data) => <DataDisplay data={data} />}
</DataContainer>

Compound Components

// Parent component
export default function Tabs({ children }: PropsWithChildren) {
const [activeTab, setActiveTab] = useState(0);

return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
{children}
</TabsContext.Provider>
);
}

// Child components
Tabs.List = function TabsList({ children }: PropsWithChildren) {
return <div className="tabs-list">{children}</div>;
};

Tabs.Tab = function Tab({ index, children }: TabProps) {
const { activeTab, setActiveTab } = useTabsContext();
return (
<button
onClick={() => setActiveTab(index)}
className={activeTab === index ? 'active' : ''}
>
{children}
</button>
);
};

Tabs.Panel = function TabPanel({ index, children }: PanelProps) {
const { activeTab } = useTabsContext();
return activeTab === index ? <div>{children}</div> : null;
};

// Usage
<Tabs>
<Tabs.List>
<Tabs.Tab index={0}>Tab 1</Tabs.Tab>
<Tabs.Tab index={1}>Tab 2</Tabs.Tab>
</Tabs.List>
<Tabs.Panel index={0}>Content 1</Tabs.Panel>
<Tabs.Panel index={1}>Content 2</Tabs.Panel>
</Tabs>

Styling Patterns

Tailwind CSS Conventions

// ✅ Good: Use Tailwind utility classes
<button className="px-4 py-2 bg-primary-700 text-white rounded-md hover:bg-primary-800">
Click me
</button>

// ✅ Good: Use clsx for conditional classes
<button
className={clsx(
'px-4 py-2 rounded-md',
variant === 'primary' && 'bg-primary-700 text-white',
variant === 'secondary' && 'bg-gray-200 text-gray-900',
disabled && 'opacity-50 cursor-not-allowed'
)}
>
Click me
</button>

// ❌ Bad: Inline styles (avoid unless necessary)
<button style={{ padding: '8px 16px', backgroundColor: '#be4ce9' }}>
Click me
</button>

Design Tokens

Use CSS variables for consistent theming:

// tailwind.config.ts
export default {
theme: {
extend: {
colors: {
primary: {
50: 'var(--color-primary-50)',
700: 'var(--color-primary-700)',
// ...
},
},
},
},
};

// Usage
<div className="bg-primary-700 text-white">
Themed content
</div>

Responsive Design

// Mobile-first approach
<div className="w-full md:w-1/2 lg:w-1/3">
Responsive width
</div>

// Responsive display
<div className="flex flex-col lg:flex-row">
<div>Column 1</div>
<div>Column 2</div>
</div>

Props Patterns

TypeScript Props Interfaces

// Basic props interface
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
onClick?: () => void;
children: React.ReactNode;
}

// Extending HTML element props
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
invalid?: boolean;
helpText?: string;
}

// Generic props
interface ListProps<T> {
items: T[];
keyExtractor: (item: T) => string;
renderItem: (item: T) => React.ReactNode;
}

Default Props

// Using default parameters
export default function Button({
variant = 'primary',
size = 'md',
disabled = false,
children,
}: ButtonProps) {
// ...
}

// Or with destructuring default
export default function Button(props: ButtonProps) {
const {
variant = 'primary',
size = 'md',
disabled = false,
children,
} = props;
// ...
}

Optional vs Required Props

interface FormProps {
// Required
onSubmit: (data: FormData) => void;

// Optional
initialValues?: FormData;
validationSchema?: Schema;

// Optional with default
disabled?: boolean; // defaults to false
}

State Management

Local State (useState)

Use for component-specific state:

export default function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<p>Count: {count}</p>
<Button onClick={() => setCount(count + 1)}>Increment</Button>
</div>
);
}

Derived State (useMemo)

Use for computed values:

export default function ProductList({ products, searchQuery }: Props) {
const filteredProducts = useMemo(() => {
return products.filter((product) =>
product.name.toLowerCase().includes(searchQuery.toLowerCase())
);
}, [products, searchQuery]);

return <List items={filteredProducts} />;
}

Context (useContext)

Use for global/shared state:

// Context provider
export default function UserProvider({ children }: PropsWithChildren) {
const [user, setUser] = useState<User | null>(null);

return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}

// Context consumer
export default function UserProfile() {
const { user } = useUserContext();

return <div>Welcome, {user?.name}!</div>;
}

TanStack Query (Server State)

Use for server data:

export default function UserList() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});

if (isLoading) return <Loader />;
if (error) return <Error error={error} />;

return <List items={data} />;
}

Performance Optimization

Memoization

// Memoize expensive computations
const filteredData = useMemo(() => {
return data.filter(item => item.active);
}, [data]);

// Memoize callbacks
const handleClick = useCallback(() => {
doSomething(value);
}, [value]);

// Memoize components
const MemoizedComponent = React.memo(MyComponent);

Code Splitting

// Lazy load components
const HeavyComponent = lazy(() => import('./HeavyComponent'));

export default function App() {
return (
<Suspense fallback={<Loader />}>
<HeavyComponent />
</Suspense>
);
}

Virtualization

// For large lists, use virtualization
import { FixedSizeList } from 'react-window';

export default function LargeList({ items }: Props) {
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
>
{({ index, style }) => (
<div style={style}>{items[index].name}</div>
)}
</FixedSizeList>
);
}

Testing Patterns

Unit Testing Components

import { render, screen, fireEvent } from '@testing-library/react';
import Button from './button.atom';

describe('Button', () => {
it('renders with children', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});

it('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);

fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});

it('is disabled when disabled prop is true', () => {
render(<Button disabled>Click me</Button>);
expect(screen.getByText('Click me')).toBeDisabled();
});
});

Accessibility Patterns

ARIA Attributes

// Buttons
<button
aria-label="Close dialog"
aria-pressed={isPressed}
onClick={handleClick}
>
<span className="material-symbols-outlined">close</span>
</button>

// Form fields
<div>
<label htmlFor="email" id="email-label">
Email Address
</label>
<input
id="email"
type="email"
aria-labelledby="email-label"
aria-describedby="email-error"
aria-invalid={!!error}
/>
{error && (
<span id="email-error" role="alert">
{error}
</span>
)}
</div>

// Loading states
<div
role="status"
aria-live="polite"
aria-busy={isLoading}
>
{isLoading ? 'Loading...' : 'Content loaded'}
</div>

Keyboard Navigation

export default function Dialog({ isOpen, onClose }: DialogProps) {
useEffect(() => {
if (isOpen) {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};

document.addEventListener('keydown', handleEscape);
return () => document.removeEventListener('keydown', handleEscape);
}
}, [isOpen, onClose]);

return isOpen ? (
<div role="dialog" aria-modal="true">
{/* Dialog content */}
</div>
) : null;
}

Best Practices

✅ Do's

  1. Keep components small and focused

    • Single Responsibility Principle
    • Easy to understand and test
  2. Use TypeScript for type safety

    • Define prop interfaces
    • Avoid any type
  3. Make components reusable

    • Generic props
    • Composition over specificity
  4. Optimize performance

    • Memoize expensive operations
    • Use React.memo for pure components
  5. Write accessible components

    • ARIA attributes
    • Keyboard navigation
  6. Document complex components

    • JSDoc comments
    • Usage examples
  7. Test components

    • Unit tests for atoms/molecules
    • Integration tests for organisms
  8. Follow naming conventions

    • PascalCase for components
    • camelCase for functions/variables

Anti-Patterns to Avoid

❌ Don'ts

  1. Deeply nested component hierarchies

    // ❌ Bad: Too many levels
    <Organism>
    <Molecule>
    <Atom>
    <SubAtom>
    <DeepNested>Content</DeepNested>
    </SubAtom>
    </Atom>
    </Molecule>
    </Organism>

    // ✅ Good: Flatten hierarchy
    <Organism>
    <Content />
    </Organism>
  2. Prop drilling

    // ❌ Bad: Passing props through many levels
    <ComponentA prop={data}>
    <ComponentB prop={data}>
    <ComponentC prop={data} />
    </ComponentB>
    </ComponentA>

    // ✅ Good: Use context
    <DataProvider value={data}>
    <ComponentA>
    <ComponentB>
    <ComponentC />
    </ComponentB>
    </ComponentA>
    </DataProvider>
  3. Mixing concerns

    // ❌ Bad: UI and business logic mixed
    export default function UserProfile() {
    const [user, setUser] = useState(null);

    useEffect(() => {
    fetch('/api/user')
    .then(res => res.json())
    .then(setUser);
    }, []);

    return <div>{user?.name}</div>;
    }

    // ✅ Good: Separate concerns
    export default function UserProfile() {
    const { user } = useUser(); // Custom hook for data fetching
    return <UserDisplay user={user} />; // Component for presentation
    }
  4. Overusing state

    // ❌ Bad: Unnecessary state
    const [fullName, setFullName] = useState('');
    const [firstName, setFirstName] = useState('');
    const [lastName, setLastName] = useState('');

    useEffect(() => {
    setFullName(`${firstName} ${lastName}`);
    }, [firstName, lastName]);

    // ✅ Good: Derived value
    const [firstName, setFirstName] = useState('');
    const [lastName, setLastName] = useState('');
    const fullName = `${firstName} ${lastName}`;
  5. Inline object/array creation in JSX

    // ❌ Bad: New object on every render
    <Component style={{ color: 'red' }} />

    // ✅ Good: Memoize or define outside
    const style = { color: 'red' };
    <Component style={style} />

Migration Guide

Converting Class Components to Functional Components

// Before: Class component
class Counter extends React.Component {
state = { count: 0 };

increment = () => {
this.setState({ count: this.state.count + 1 });
};

render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}

// After: Functional component
export default function Counter() {
const [count, setCount] = useState(0);

const increment = () => {
setCount(count + 1);
};

return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}

Refactoring Large Components

// Before: Large monolithic component
export default function UserDashboard() {
// 500+ lines of code
// Multiple responsibilities
// Hard to test and maintain
}

// After: Split into smaller components
export default function UserDashboard() {
return (
<DashboardTemplate>
<UserProfile />
<UserStats />
<RecentActivity />
</DashboardTemplate>
);
}

Conclusion

Following these component patterns ensures:

  • Consistency across the codebase
  • Scalability for future features
  • Maintainability for the team
  • Performance for users
  • Accessibility for everyone

Remember: Start small, compose large.


Questions or Suggestions?
Contact the development team or create an issue in the repository.

Last Updated: October 28, 2025
Version: 1.0.0