Baldr Dashboard - Component Patterns Guide
Version: 1.0.0
Last Updated: October 28, 2025
Maintainer: Development Team
Table of Contents
- Introduction
- Atomic Design System
- Component Hierarchy
- Naming Conventions
- File Structure
- Atoms
- Molecules
- Organisms
- Templates
- When to Create New Components
- Component Composition
- Styling Patterns
- Props Patterns
- State Management
- Performance Optimization
- Testing Patterns
- Accessibility Patterns
- Best Practices
- Anti-Patterns to Avoid
- 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
- Single Responsibility: Each component does one thing well
- Composition over Inheritance: Build complex UIs by composing simple components
- Props over State: Prefer controlled components
- Styling Consistency: Use Tailwind CSS and design tokens
- 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 variantsinput.atom.tsx- Text input fieldlink.atom.tsx- Navigation linkparagraph.atom.tsx- Text paragraphcheckbox.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 + ErrorlanguageSelector.molecule.tsx- Dropdown + IconscheckboxField.molecule.tsx- Checkbox + LabeltranslationTabs.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 actionsmenu.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 layoutauthTemplate.template.tsx- Authentication pages layoutcontentTemplate.template.tsx- Content pages layout
Naming Conventions
File Naming
All component files follow this pattern:
<componentName>.<level>.tsx
Examples:
button.atom.tsxinputField.molecule.tsxtableManager.organism.tsxdashboardTemplate.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
clsxfor 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>
);
}
Menu with Dynamic Content Pattern
/**
* 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
-
Does this component already exist?
- Search
app/components/before creating - Check if existing component can be extended
- Search
-
Can it be broken down further?
- If yes → Create smaller atoms/molecules first
- If no → It's probably an atom
-
Is it reusable?
- Yes → Make it generic, add to atoms/molecules
- No → Consider making it an organism or page-specific component
-
Does it contain business logic?
- Yes → Organism or higher
- No → Atom or molecule
-
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
-
Keep components small and focused
- Single Responsibility Principle
- Easy to understand and test
-
Use TypeScript for type safety
- Define prop interfaces
- Avoid
anytype
-
Make components reusable
- Generic props
- Composition over specificity
-
Optimize performance
- Memoize expensive operations
- Use React.memo for pure components
-
Write accessible components
- ARIA attributes
- Keyboard navigation
-
Document complex components
- JSDoc comments
- Usage examples
-
Test components
- Unit tests for atoms/molecules
- Integration tests for organisms
-
Follow naming conventions
- PascalCase for components
- camelCase for functions/variables
Anti-Patterns to Avoid
❌ Don'ts
-
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> -
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> -
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
} -
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}`; -
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