> For the complete documentation index, see [llms.txt](https://docs.wellcomecollection.org/request-for-comments-rfcs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.wellcomecollection.org/request-for-comments-rfcs/086-item-viewer-refactor/16-future-improvements.md).

# Future Improvements

[← Back to Index](/request-for-comments-rfcs/086-item-viewer-refactor.md)

After the main refactoring succeeds, consider these architectural improvements.

## 1. Further Context Splitting

### Problem: Components with Two Faces

Several components have completely different rendering logic based on boolean conditionals, violating the Single Responsibility Principle.

**Example: MainViewer**

`MainViewer.tsx` has two fundamentally different implementations:

```typescript
if (hasOnlyRenderableImages) {
  // Mode A: Virtualized image scrolling with FixedSizeList
  return <FixedSizeList>{ItemRenderer}</FixedSizeList>;
}

// Mode B: Direct item rendering with IIIFItem components
return <>{displayItems.map(item => <IIIFItem />)}</>;
```

These are two different components with:

* Different data requirements
* Different performance characteristics
* Different rendering strategies
* Different testing needs

### Recommendation: Split into Separate Components

**Before:**

```
MainViewer
  ├── if (hasOnlyRenderableImages) { FixedSizeList logic }
  └── else { IIIFItem rendering }
```

**After:**

```
MainViewer (router component)
  ├── VirtualizedImageViewer (for image-only works)
  └── PaginatedItemViewer (for mixed content/archives)
```

**Benefits:**

* **Clarity:** Each component has one clear purpose
* **Testability:** Test each mode independently
* **Performance:** Separate bundle chunks, load only what's needed
* **Maintainability:** Changes to one mode don't affect the other
* **Type safety:** Each component has its own specific props

### Implementation

**File:** `content/webapp/views/pages/works/work/IIIFViewer/MainViewer.tsx`

```typescript
import { FunctionComponent } from 'react';
import { useItemViewerContext } from '@weco/content/contexts/ItemViewerContext';

// New components (in separate files)
import VirtualizedImageViewer from './VirtualizedImageViewer';
import PaginatedItemViewer from './PaginatedItemViewer';

const MainViewer: FunctionComponent = () => {
  const { hasOnlyRenderableImages } = useItemViewerContext();
  
  // Router component: decide which implementation to use
  if (hasOnlyRenderableImages) {
    return <VirtualizedImageViewer />;
  }
  
  return <PaginatedItemViewer />;
};

export default MainViewer;
```

**New file:** `VirtualizedImageViewer.tsx`

```typescript
// Only handles image-only works with virtualized scrolling
import { FunctionComponent } from 'react';
import { FixedSizeList } from 'react-window';

const VirtualizedImageViewer: FunctionComponent = () => {
  const {
    mainAreaWidth,
    mainAreaHeight,
    canvases,
    rotatedImages,
    // ... only props needed for this mode
  } = useItemViewerContext();
  
  return (
    <FixedSizeList
      width={mainAreaWidth}
      height={mainAreaHeight}
      itemCount={canvases?.length || 0}
      // ... virtualization logic
    >
      {ItemRenderer}
    </FixedSizeList>
  );
};
```

**New file:** `PaginatedItemViewer.tsx`

```typescript
// Only handles mixed content and archives
import { FunctionComponent } from 'react';

const PaginatedItemViewer: FunctionComponent = () => {
  const {
    currentCanvas,
    canvases,
    canvas,
    // ... only props needed for this mode
  } = useItemViewerContext();
  
  const displayItems = currentCanvas ? getDisplayItems(currentCanvas) : [];
  
  return (
    <>
      {displayItems.map((item, i) => (
        <IIIFItem
          item={item}
          canvas={currentCanvas}
          // ... item rendering
        />
      ))}
    </>
  );
};
```

### Other Candidates for Splitting

Look for these patterns:

* Large if/else blocks returning different JSX
* Components with multiple "modes" or "types"
* Boolean props that drastically change behaviour (`hasOnlyRenderableImages`, `isArchive`, `isRestricted`)
* Different event handlers based on conditionals

**Potential candidates in ItemViewer:**

* `IIIFViewer.tsx` - different rendering for image-only vs mixed content
* Any component checking `hasOnlyRenderableImages` extensively

### When NOT to Split

Don't split if:

* It's just conditional rendering of small UI elements (buttons, labels)
* The modes share significant logic
* The conditional is for progressive enhancement (browser support)
* Splitting would create more complexity than it solves

### Testing Strategy

After splitting:

1. Test each component independently with focused test suites
2. Test the router component's decision logic
3. Verify no regressions in E2E tests
4. Check bundle size hasn't increased significantly

***

## 2. Further Context Splitting

If context grows too large or causes performance issues:

```
ItemViewerContext (current)
  ↓
Split into:
  ├── ItemViewerDataContext (work, manifest, derived data)
  └── ItemViewerUIContext (sidebar, zoom, grid, fullscreen)
```

**Benefit:** More granular re-rendering control

**When to do this:**

* React Profiler shows excessive re-renders
* UI state changes cause data components to re-render unnecessarily
* Context has >20 values

***

## 3. TypeScript Discriminated Unions for Viewer Modes

Make viewer modes type-safe with discriminated unions:

```typescript
type ViewerMode = 
  | { 
      type: 'image-gallery';
      hasOnlyRenderableImages: true;
      canvases: TransformedCanvas[];
    }
  | { 
      type: 'archive';
      hasOnlyRenderableImages: false;
      archiveTree: UiTree;
      currentCanvas: TransformedCanvas;
    }
  | { 
      type: 'restricted';
      hasOnlyRenderableImages: false;
      authStatus: AuthStatus;
    };

type ItemViewerContextProps = {
  // ... common props
  mode: ViewerMode;
};
```

**Benefit:**

* TypeScript enforces that archive-specific props only exist in archive mode
* Impossible to use `archiveTree` in image-gallery mode (compile error)
* Self-documenting code

***

## 4. Extract Complex State Machines

Use state machine libraries for complex UI state:

```typescript
import { useMachine } from '@xstate/react';

const viewerMachine = createMachine({
  initial: 'loading',
  states: {
    loading: {
      on: { LOADED: 'viewing' }
    },
    viewing: {
      on: { 
        ZOOM: 'zoomed',
        FULLSCREEN: 'fullscreen',
        ERROR: 'error'
      }
    },
    zoomed: {
      on: { CLOSE: 'viewing' }
    },
    // ...
  }
});
```

**When:** Complex state transitions with many edge cases

***

## 5. Performance Monitoring

Add performance instrumentation:

```typescript
import { useReportEffect } from '@weco/common/hooks/useReportEffect';

function ItemViewerContextProvider({ children }) {
  useReportEffect('ItemViewerContext render');
  
  // Mark expensive calculations
  const derivedData = useMemo(() => {
    performance.mark('derive-canvas-data-start');
    const result = deriveCanvasData(manifest, query);
    performance.mark('derive-canvas-data-end');
    performance.measure(
      'derive-canvas-data',
      'derive-canvas-data-start',
      'derive-canvas-data-end'
    );
    return result;
  }, [manifest, query]);
  
  // ...
}
```

Track in production to identify bottlenecks.

***

## Priority Order

1. **Component splitting** - High impact, relatively easy, improves clarity immediately
2. **Context splitting** - Only if performance issues detected
3. **Discriminated unions** - Nice to have, consider if making large type changes
4. **State machines** - Only if state management becomes very complex
5. **Performance monitoring** - Implement if performance concerns arise

Start with component splitting if the main refactoring goes well.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.wellcomecollection.org/request-for-comments-rfcs/086-item-viewer-refactor/16-future-improvements.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
