# Container/Presentation Pattern

## Overview

The Container/Presentation pattern separates component logic from presentation, improving testability, reusability, and maintainability. This pattern is now the standard for complex components in this codebase.

## Motivation

### Benefits

- **Testability**: Pure presentation components can be tested independently with simple prop assertions
- **Reusability**: Presentation components can be reused with different data sources or logic
- **Maintainability**: Clear separation makes it easier to locate and modify specific concerns
- **Performance**: Easier to optimize rendering with React.memo on pure components

### When to Use

Use this pattern for components that have:

- Business logic or state management
- Data fetching or API calls
- Analytics tracking
- Complex event handlers
- Custom hooks usage
- Dynamic imports or side effects

Simple presentational components (e.g., `Button`, `Avatar`) can remain as single files.

## Folder Structure

Each component following this pattern should have this structure:

```
app/components/[ComponentName]/
  ├── index.tsx                    # Exports container as default
  ├── [ComponentName].container.tsx # Logic, hooks, state management
  ├── [ComponentName].view.tsx     # Pure presentation component
  └── [ComponentName].types.ts     # Shared TypeScript types
```

### File Responsibilities

#### `index.tsx`

- Exports the container component as the default export
- Optionally exports types for external use
- Maintains backward compatibility with existing import paths

```typescript
export { default } from "./AskOrganizer.container";
export type { AskOrganizerProps } from "./AskOrganizer.types";
```

#### `[ComponentName].container.tsx`

**Contains all logic:**

- React hooks (`useState`, `useEffect`, custom hooks)
- Event handlers and business logic
- Data fetching and API calls
- Analytics tracking
- State management
- Computed values and derived state
- Side effects

**Should NOT contain:**

- JSX layout details (beyond composing the view)
- Inline styles or complex className logic (pass as props)
- Direct DOM manipulation

```typescript
"use client";

import { memo } from "react";
import { useAnalytics } from "../../hooks";
import { AskOrganizerView } from "./AskOrganizer.view";
import type { AskOrganizerProps } from "./AskOrganizer.types";

function AskOrganizerContainer(props: AskOrganizerProps) {
  const { trackEvent } = useAnalytics();

  const handleContactClick = () => {
    trackEvent({
      event: "contact_button_click",
      category: "engagement",
      component: "AskOrganizer",
    });
    // ... additional logic
  };

  // Compute derived props
  const variantStyles = computeVariantStyles(props.variant);

  return (
    <AskOrganizerView
      {...props}
      onContactClick={handleContactClick}
      variantStyles={variantStyles}
    />
  );
}

export default memo(AskOrganizerContainer);
```

#### `[ComponentName].view.tsx`

**Pure presentation:**

- Receives all data via props
- Renders JSX based on props
- No hooks, no state, no side effects
- Only imports other presentational components

**Should NOT contain:**

- `useState`, `useEffect`, or any hooks
- Event handler implementations (receive as callbacks)
- Data fetching or API calls
- Analytics tracking
- Business logic

```typescript
import ContentLockup from "../ContentLockup";
import Button from "../Button";
import type { AskOrganizerViewProps } from "./AskOrganizer.types";

export function AskOrganizerView({
  title,
  subtitle,
  description,
  buttonText,
  buttonHref,
  variant,
  onContactClick,
  variantStyles,
  ...props
}: AskOrganizerViewProps) {
  return (
    <section className={variantStyles.container}>
      <ContentLockup
        title={title}
        subtitle={subtitle}
        description={description}
      />
      <Button
        href={buttonHref}
        onClick={onContactClick}
      >
        {buttonText}
      </Button>
    </section>
  );
}
```

#### `[ComponentName].types.ts`

- Shared TypeScript interfaces and types
- Public props interface (used by consumers)
- Internal view props (used between container and view)
- Any utility types specific to the component

```typescript
export interface AskOrganizerProps {
  title?: string;
  subtitle?: string;
  buttonText?: string;
  buttonHref?: string;
  variant?: "centered" | "left-aligned" | "compact" | "inverse";
  onContactClick?: (data: ContactClickData) => void;
}

export interface AskOrganizerViewProps extends AskOrganizerProps {
  onContactClick: () => void;
  variantStyles: {
    container: string;
    buttonContainer: string;
  };
}
```

## Rules of Thumb

### Container Components

✅ **DO:**

- Use React hooks (`useState`, `useEffect`, custom hooks)
- Handle all event handlers and business logic
- Fetch data and manage loading states
- Track analytics events
- Compute derived values from props/state
- Compose the view component with computed props

❌ **DON'T:**

- Include complex JSX layout (delegate to view)
- Mix presentation logic with business logic
- Access DOM directly (use refs when necessary)

### View Components

✅ **DO:**

- Receive all data via props
- Render JSX based on props
- Import only presentational components
- Use simple conditional rendering
- Accept callback props for user interactions

❌ **DON'T:**

- Use any React hooks
- Manage state or side effects
- Fetch data or make API calls
- Track analytics directly
- Implement business logic
- Access browser APIs directly

## Example: AskOrganizer

### Before (Monolithic)

```typescript
"use client";

import { memo } from "react";
import { useAnalytics } from "../hooks";
import ContentLockup from "./ContentLockup";
import Button from "./Button";

const AskOrganizer = memo(({ title, variant, ...props }) => {
  const { trackEvent } = useAnalytics();

  const handleContactClick = () => {
    trackEvent({ event: "contact_click", component: "AskOrganizer" });
  };

  return (
    <section>
      <ContentLockup title={title} />
      <Button onClick={handleContactClick}>Ask an organizer</Button>
    </section>
  );
});
```

### After (Container/Presentation)

**AskOrganizer.container.tsx:**

```typescript
"use client";

import { memo } from "react";
import { useAnalytics } from "../../hooks";
import { AskOrganizerView } from "./AskOrganizer.view";
import type { AskOrganizerProps } from "./AskOrganizer.types";

function AskOrganizerContainer(props: AskOrganizerProps) {
  const { trackEvent } = useAnalytics();

  const handleContactClick = () => {
    trackEvent({ event: "contact_click", component: "AskOrganizer" });
  };

  return <AskOrganizerView {...props} onContactClick={handleContactClick} />;
}

export default memo(AskOrganizerContainer);
```

**AskOrganizer.view.tsx:**

```typescript
import ContentLockup from "../ContentLockup";
import Button from "../Button";
import type { AskOrganizerViewProps } from "./AskOrganizer.types";

export function AskOrganizerView({
  title,
  onContactClick,
  ...props
}: AskOrganizerViewProps) {
  return (
    <section>
      <ContentLockup title={title} />
      <Button onClick={onContactClick}>Ask an organizer</Button>
    </section>
  );
}
```

## Migration Checklist

When converting an existing component to this pattern:

- [ ] **Identify separation points**
  - [ ] List all hooks and state management
  - [ ] List all event handlers and business logic
  - [ ] List all data fetching and side effects
  - [ ] Identify pure presentation JSX

- [ ] **Create folder structure**
  - [ ] Create `[ComponentName]/` folder
  - [ ] Create `[ComponentName].types.ts` with shared types
  - [ ] Create `[ComponentName].view.tsx` with pure presentation
  - [ ] Create `[ComponentName].container.tsx` with all logic
  - [ ] Create `index.tsx` exporting container

- [ ] **Extract types**
  - [ ] Move component props interface to `types.ts`
  - [ ] Create view props interface extending container props
  - [ ] Export types from `index.tsx` for external use

- [ ] **Move presentation to view**
  - [ ] Copy JSX to view component
  - [ ] Remove all hooks and state
  - [ ] Replace event handlers with callback props
  - [ ] Replace computed values with props
  - [ ] Ensure view is a pure function

- [ ] **Move logic to container**
  - [ ] Move all hooks to container
  - [ ] Move event handlers to container
  - [ ] Move data fetching to container
  - [ ] Compute derived props in container
  - [ ] Render view component with computed props

- [ ] **Update exports**
  - [ ] Export container as default from `index.tsx`
  - [ ] Export types from `index.tsx`
  - [ ] Delete original component file
  - [ ] Verify import paths still work

- [ ] **Update tests**
  - [ ] Verify tests still pass (imports should resolve automatically)
  - [ ] Update any tests that relied on implementation details
  - [ ] Add tests for view component with mocked props if needed

- [ ] **Update Storybook**
  - [ ] Verify stories still work (imports should resolve automatically)
  - [ ] Optionally add view-only stories with mocked props

## Refactored Components

The following components have been refactored to use this pattern:

- ✅ **AskOrganizer** - Analytics tracking and event handlers
- ✅ **NumberedCards** - Schema generation with `useSchemaData` hook
- ✅ **FeatureGrid** - Memoized feature data structures
- ✅ **WebVitalsDashboard** - Dynamic imports, data fetching, complex state
- ✅ **Select** - Complex form state management with refs and keyboard navigation

These components serve as reference implementations for the pattern.

## Remaining Components

The following components are candidates for future conversion:

### High Priority (Complex Logic)

- `Header` / `HomeHeader` - Navigation state, conditional rendering logic
- `MenuBar` - Menu state management, keyboard navigation
- `ContextMenu` - Positioning logic, click outside handling
- `RadioGroup` - Group state management
- `ToggleGroup` - Group state management

### Medium Priority (Some Logic)

- `ContentContainer` - Data fetching or transformation
- `RelatedArticles` - Data fetching, filtering logic
- `RuleStack` - Complex rendering logic
- `LogoWall` - Animation or interaction logic

### Low Priority (Mostly Presentational)

- `Button`, `Avatar`, `Checkbox`, `Input`, `TextArea` - Simple presentational components
- `Separator`, `SectionHeader`, `SectionNumber` - Pure presentation
- `QuoteBlock`, `QuoteDecor`, `HeroDecor` - Decorative components

## Best Practices

1. **Start with complex components** - Components with the most logic benefit most from separation
2. **Keep it simple** - Don't over-engineer simple presentational components
3. **Maintain backward compatibility** - Import paths should remain unchanged
4. **Test both layers** - Test container for logic, view for presentation
5. **Document the pattern** - Add comments explaining non-obvious prop flows
6. **Use TypeScript strictly** - Leverage types to enforce the separation

## Additional Resources

- [React Container/Presenter Pattern](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)
- [Separation of Concerns in React](https://react.dev/learn/thinking-in-react)

---

**Last Updated**: April 2025  
**Maintained by**: CommunityRule Development Team
