# React Memoization Optimization Guide

## Overview

This document outlines the memoization optimizations applied to the frontend components to reduce unnecessary re-renders and improve performance.

## Optimized Components

### 1. ItemCard (ItemsKanbanView)

**Location:** `src/views/ItemsKanbanView.tsx`

**Optimization Strategy:**

- Wrapped component with `memo()` with custom comparison function
- Added `useCallback` for drag handler to maintain reference equality
- Custom comparison checks only critical props: `id`, `title`, `type`, `status`, `priority`, `owner`

**Before:**

```typescript
function ItemCard({ item, onDragStart }: ItemCardProps) {
  return (
    <div draggable onDragStart={() => onDragStart(item)}>
      {/* ... */}
    </div>
  )
}
```

**After:**

```typescript
const ItemCard = memo(
  function ItemCard({ item, onDragStart }: ItemCardProps) {
    const handleDragStart = useCallback(() => {
      onDragStart(item);
    }, [item, onDragStart]);

    return (
      <div draggable onDragStart={handleDragStart}>
        {/* ... */}
      </div>
    )
  },
  (prev, next) => {
    // Custom comparison - only re-render if critical props change
    return (
      prev.item.id === next.item.id &&
      prev.item.title === next.item.title &&
      prev.item.type === next.item.type &&
      prev.item.status === next.item.status &&
      prev.item.priority === next.item.priority &&
      prev.item.owner === next.item.owner
    );
  }
);
```

**Expected Improvement:** 60-80% reduction in ItemCard re-renders when filtering or sorting

### 2. TreeItem (ItemsTreeView)

**Location:** `src/views/ItemsTreeView.tsx`

**Optimization Strategy:**

- Wrapped component with `memo()` and custom comparison function
- Added `useCallback` for toggle handler
- Custom comparison intelligently checks Set membership instead of reference
- Prevents unnecessary re-renders of tree nodes during parent state changes

**Before:**

```typescript
function TreeItem({ node, expandedIds, onToggleExpand }: TreeItemProps) {
  const { item, children, level } = node;
  const isExpanded = expandedIds.has(item.id);

  return (
    <div>
      <button onClick={() => onToggleExpand(item.id)}>
        {isExpanded ? '▼' : '▶'}
      </button>
      {/* ... */}
    </div>
  );
}
```

**After:**

```typescript
const TreeItem = memo(
  function TreeItem({ node, expandedIds, onToggleExpand }: TreeItemProps) {
    const { item, children, level } = node;
    const isExpanded = expandedIds.has(item.id);

    const handleToggle = useCallback(() => {
      onToggleExpand(item.id);
    }, [item.id, onToggleExpand]);

    return (
      <div>
        <button onClick={handleToggle}>
          {isExpanded ? '▼' : '▶'}
        </button>
        {/* ... */}
      </div>
    );
  },
  (prev, next) => {
    // Smart comparison: checks if expansion state changed
    return (
      prev.node.item.id === next.node.item.id &&
      prev.node.level === next.node.level &&
      prev.node.children.length === next.node.children.length &&
      prev.expandedIds.has(prev.node.item.id) ===
        next.expandedIds.has(next.node.item.id)
    );
  }
);
```

**Expected Improvement:** 70-80% reduction in TreeItem re-renders during expand/collapse operations

### 3. VirtualTableRow (ItemsTableView)

**Location:** `src/views/ItemsTableView.tsx`

**Optimization Strategy:**

- Already had `memo()` - enhanced with better custom comparison
- Added `useCallback` for navigation and delete handlers
- Virtual scrolling already implemented (uses `@tanstack/react-virtual`)
- Custom comparison prevents unnecessary row re-renders in large lists

**Before:**

```typescript
const VirtualTableRow = memo(
  ({ item, onDelete, onNavigate }: VirtualTableRowProps) => {
    return (
      <TableRow>
        <TableCell>
          <button onClick={() => onNavigate(`/items/${item.id}`)}>
            {item.title}
          </button>
        </TableCell>
        {/* ... */}
      </TableRow>
    );
  }
);
```

**After:**

```typescript
const VirtualTableRow = memo(
  ({ item, onDelete, onNavigate }: VirtualTableRowProps) => {
    const handleNavigate = useCallback(() => {
      onNavigate(`/items/${item.id}`);
    }, [item.id, onNavigate]);

    const handleDelete = useCallback(() => {
      onDelete(item.id);
    }, [item.id, onDelete]);

    return (
      <TableRow>
        <TableCell>
          <button onClick={handleNavigate}>
            {item.title}
          </button>
        </TableCell>
        {/* ... */}
      </TableRow>
    );
  },
  (prev, next) => {
    // Custom comparison for table rows
    return (
      prev.item.id === next.item.id &&
      prev.item.title === next.item.title &&
      prev.item.type === next.item.type &&
      prev.item.status === next.item.status &&
      prev.item.priority === next.item.priority &&
      prev.item.owner === next.item.owner
    );
  }
);
```

**Expected Improvement:** 60-75% reduction in row re-renders in virtual list (especially with 100+ items)

### 4. Event Handlers (All Views)

**Optimization Strategy:**

- Wrapped all event handlers with `useCallback` hook
- Dependencies carefully maintained to prevent stale closures
- Stable references reduce child component re-renders

**ItemsKanbanView handlers:**

```typescript
const handleDrop = useCallback(
  async (newStatus: ItemStatus) => {
    // ... implementation
  },
  [draggedItem, updateItem],
);

const handleDragStart = useCallback((item: Item) => {
  setDraggedItem(item);
}, []);
```

**ItemsTableView handlers:**

```typescript
const handleCreate = useCallback(async () => {
  // ... implementation
}, [
  effectiveProjectId,
  newTitle,
  newType,
  newStatus,
  newPriority,
  view,
  type,
  createItem,
  closeCreateModal,
]);

const handleDelete = useCallback(
  async (id: string) => {
    // ... implementation
  },
  [deleteItem],
);
```

### 5. useMemo for Computed Values

**Already Implemented:**
All views already use `useMemo` for:

- `filteredItems` - prevents recalculation on unrelated state changes
- `itemsByStatus` - groups items only when filteredItems change
- `filteredAndSortedItems` - combines filtering and sorting logic
- `treeNodes` - builds tree structure only when items change

## Key Principles

### 1. Custom Comparison Functions

Instead of relying on reference equality, custom comparison functions check actual data equality:

```typescript
(prev, next) => {
  return (
    prev.item.id === next.item.id && prev.item.title === next.item.title
    // ... only check props that affect rendering
  );
};
```

### 2. useCallback Dependencies

Always include all dependencies that the callback uses:

```typescript
const handleClick = useCallback(() => {
  doSomething(item.id); // item must be in dependencies
}, [item.id]); // ✓ Correct
```

### 3. Stable List/Set References

When passing collections as props, ensure they maintain referential equality:

- Use `useMemo` to wrap array creation
- Don't create new objects in render functions
- Use Set's built-in methods for comparisons, not reference checks

## Performance Metrics

### Expected Improvements (Before vs After)

| Component       | Scenario           | Reduction   | Details                         |
| --------------- | ------------------ | ----------- | ------------------------------- |
| ItemCard        | Filter/Sort + Drag | 60-80%      | Per-card re-renders eliminated  |
| TreeItem        | Expand/Collapse    | 70-80%      | Only affected nodes re-render   |
| VirtualTableRow | Sort/Filter        | 60-75%      | Virtual scrolling + memoization |
| Event Handlers  | All interactions   | Stable refs | Child re-renders prevented      |

### Profiling with React DevTools

To measure improvements:

```typescript
import { Profiler } from 'react';

<Profiler id="ItemsKanban" onRender={(id, phase, actualDuration) => {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
}}>
  <ItemsKanbanView />
</Profiler>
```

## Testing Optimizations

Run the memoization tests:

```bash
bun run test src/__tests__/components/memoization.test.tsx
```

Test verifies:

- Components skip re-renders when props haven't changed
- useCallback maintains stable references
- useMemo only recomputes when dependencies change
- Custom comparisons work correctly

## Common Pitfalls

### ❌ Inline Object Creation

```typescript
// Bad - creates new object every render
<ItemCard item={item} onDragStart={setDraggedItem} />
```

### ✅ Stable Object References

```typescript
// Good - reuse same reference
const handleDragStart = useCallback((item: Item) => {
  setDraggedItem(item);
}, []);
<ItemCard item={item} onDragStart={handleDragStart} />
```

### ❌ Missing Dependencies

```typescript
// Bad - stale closure, item never updates
const handleClick = useCallback(() => {
  console.log(item.id);
}, []); // Missing item dependency
```

### ✅ Complete Dependencies

```typescript
// Good - callback updates when item changes
const handleClick = useCallback(() => {
  console.log(item.id);
}, [item.id]); // Included dependency
```

## Future Optimizations

1. **Suspense & Code Splitting:** Lazy load views and components
2. **Virtual Scrolling:** Already implemented in ItemsTableView
3. **Windowing:** Implement for very large tree structures
4. **State Persistence:** Minimize filter/sort recalculations
5. **Selective Re-renders:** Use context with atomic state updates

## References

- [React.memo Documentation](https://react.dev/reference/react/memo)
- [useCallback Hook Documentation](https://react.dev/reference/react/useCallback)
- [useMemo Hook Documentation](https://react.dev/reference/react/useMemo)
- [React Profiler API](https://react.dev/reference/react/Profiler)

## Monitoring Performance

Add React DevTools Profiler in development:

```bash
# Install React DevTools Chrome Extension
# Open: Chrome DevTools > Profiler tab
# Record interactions and analyze component render times
```

## Maintenance

When modifying optimized components:

1. **Test locally** with React DevTools Profiler
2. **Verify dependencies** in useCallback/useMemo
3. **Check custom comparisons** match updated props
4. **Run test suite** to catch regressions
5. **Profile E2E flows** to catch performance degradation

---

**Last Updated:** 2026-01-29
**Impact:** Estimated 60-80% reduction in unnecessary re-renders
