Skip to content

[FRONTEND] Refactor Design Hooks for Better Separation of Concerns #440

@Andreawingardh

Description

@Andreawingardh

Refactor Design Hooks for Better Separation of Concerns

Context

Currently, our design-related hooks mix multiple concerns, leading to tight coupling and reduced maintainability. This refactoring will separate responsibilities and make the codebase more modular.

Current State:

  • useCustomDesign - Manages 3D scene UI state + serialization logic
  • useDesign - Handles CRUD operations + screenshot uploads + some state management

Problem:

  • Changing screenshot logic requires touching the CRUD hook
  • Serialization format changes affect the UI state hook
  • Difficult to test concerns in isolation
  • Code reuse is limited

Why This Matters:
This technical debt is manageable now, but as features grow (PDF exports, design templates, collaborative editing), the coupling will make changes increasingly difficult and error-prone.


Goals

  1. Single Responsibility Principle - Each hook should have one clear purpose
  2. Easier Testing - Isolated concerns are easier to unit test
  3. Better Reusability - Screenshot logic could be used for other features
  4. Maintainability - Changes to one concern don't ripple through unrelated code

Proposed Architecture

New Hook Structure

useDesignData        → Pure CRUD operations (fetch, create, update, delete)
useScreenshots       → Screenshot capture and upload logic
useDesignSerializer  → Convert between CustomDesign and DesignData JSON
useCustomDesign      → UI state management only (no serialization)

Implementation Steps

Step 1: Extract Screenshot Logic (1-2 hours)

Goal: Create useScreenshots hook to isolate all screenshot-related operations.

Tasks:

  • Create hooks/useScreenshots.ts
  • Move screenshot upload logic from useDesign to useScreenshots
  • Export methods:
    • uploadScreenshots(designId, fullBlob, thumbnailBlob) → returns { screenshotUrl, thumbnailUrl }
    • captureScreenshot() → captures from canvas (if needed)
  • Handle loading/error states within the hook
  • Update useDesign to use the new useScreenshots hook
  • Test that screenshot uploads still work

Files to modify:

  • Create: src/hooks/useScreenshots.ts
  • Modify: src/hooks/designs/useDesign.ts

Success Criteria:

  • useDesign no longer contains screenshot upload code
  • All existing screenshot functionality still works
  • Screenshot logic can be reused in other contexts

Step 2: Create Pure CRUD Hook (2-3 hours)

Goal: Create useDesignData hook for all database operations.

Tasks:

  • Create hooks/useDesignData.ts
  • Move all DesignService API calls from components and useDesign
  • Export methods:
    • getAllDesigns() → returns DesignDto[]
    • getDesignById(id) → returns DesignDto
    • createDesign(data) → returns DesignDto
    • updateDesign(id, data) → returns DesignDto
    • deleteDesign(id) → returns void
    • updateDesignName(id, name) → returns DesignDto
  • Manage shared state: designs, currentDesign, isLoading, error
  • Handle cache invalidation (e.g., refresh list after delete)
  • Update components to use useDesignData instead of direct DesignService calls

Files to modify:

  • Create: src/hooks/useDesignData.ts
  • Modify: src/components/UserDesigns.tsx
  • Modify: src/components/SingleDesignView.tsx
  • Modify: src/hooks/designs/useDesign.ts

Success Criteria:

  • No DesignService calls in components
  • All CRUD operations go through useDesignData
  • Consistent error handling across all operations
  • Cache updates properly after mutations

Step 3: Extract Serialization Logic (1 hour)

Goal: Separate serialization from UI state management.

Tasks:

  • Create lib/designSerializer.ts (pure functions, not a hook)
  • Move serialization logic from useCustomDesign:
    • serializeDesign(customDesign) → returns JSON string (was getSceneData)
    • deserializeDesign(jsonString) → returns CustomDesign object (was loadSceneData)
  • Update useCustomDesign to use these functions
  • Remove getSceneData and loadSceneData from useCustomDesign
  • Update all callers to use the new functions

Files to modify:

  • Create: src/lib/designSerializer.ts
  • Modify: src/hooks/designs/useCustomDesign.ts
  • Modify: Any components calling getSceneData or loadSceneData

Success Criteria:

  • Serialization logic is testable without React
  • useCustomDesign only manages UI state
  • Changing serialization format doesn't require hook changes

Step 4: Refactor useDesign as Orchestrator (1-2 hours)

Goal: Transform useDesign into a high-level orchestrator that composes other hooks.

Current responsibilities:

  • CRUD operations
  • Screenshot uploads
  • Scene data management
  • State management

New responsibilities:

  • Orchestrate useDesignData + useScreenshots + useCustomDesign
  • Provide high-level operations like saveDesignWithScreenshot()
  • Coordinate between different concerns

Tasks:

  • Import and use useDesignData, useScreenshots
  • Refactor methods to compose lower-level hooks:
  async function createDesignWithScreenshot(name, sceneData, screenshots) {
    const design = await designData.createDesign({ name, sceneData });
    if (screenshots && design.id) {
      const urls = await screenshots.uploadScreenshots(design.id, ...);
      return await designData.updateDesign(design.id, urls);
    }
    return design;
  }
  • Remove duplicate state management
  • Keep only orchestration logic

Files to modify:

  • Modify: src/hooks/designs/useDesign.ts

Success Criteria:

  • useDesign delegates to specialized hooks
  • No direct DesignService or screenshot upload code in useDesign
  • High-level operations still work as before

Step 5: Testing & Documentation (1-2 hours)

Tasks:

  • Write unit tests for designSerializer.ts
  • Write tests for useDesignData (mock API calls)
  • Write tests for useScreenshots (mock upload)
  • Update component tests to use new hooks
  • Document hook responsibilities in each file
  • Update README with hook architecture diagram
  • Add JSDoc comments to public methods

Success Criteria:

  • All existing tests pass
  • New hooks have test coverage
  • Architecture is documented

Testing Strategy

Before Refactoring:

  • Run full test suite - ensure all tests pass
  • Manually test: create design, save design, load design, delete design
  • Verify screenshot uploads work
  • Note any flaky behaviors

During Refactoring:

  • Run tests after each step
  • Manually test affected features after each step
  • Use feature branches for each step
  • Don't merge until tests pass

After Refactoring:

  • Full regression test of design workflows
  • Performance check (no new performance issues)
  • Code review for architectural clarity

Migration Path

This refactoring can be done incrementally:

  1. Phase 1: Extract useScreenshots (doesn't affect other code much)
  2. Phase 2: Create useDesignData (can coexist with old code)
  3. Phase 3: Extract serialization (low risk, pure functions)
  4. Phase 4: Refactor useDesign (final integration)
  5. Phase 5: Testing and cleanup

Each phase can be a separate PR for easier review.


Potential Issues

Issue: Breaking Changes

Risk: Components rely on current useDesign interface
Mitigation: Keep same public API initially, only change internals

Issue: State Management Conflicts

Risk: Multiple hooks managing similar state
Mitigation: Make useDesignData the single source of truth for design data

Issue: Performance

Risk: More hooks = more re-renders?
Mitigation: Careful use of useMemo, useCallback, and ensure hooks don't cause unnecessary renders


Definition of Done

  • All hooks follow Single Responsibility Principle
  • No DesignService calls in components
  • Screenshot logic is reusable
  • Serialization is testable without React
  • All existing features work identically
  • Test coverage maintained or improved
  • Documentation updated
  • Code reviewed and approved
  • No performance regressions

Estimated Time: 6-10 hours

  • Step 1: 1-2 hours
  • Step 2: 2-3 hours
  • Step 3: 1 hour
  • Step 4: 1-2 hours
  • Step 5: 1-2 hours

References

  • Current hooks: src/hooks/designs/useDesign.ts, src/hooks/designs/useCustomDesign.ts
  • Related components: UserDesigns.tsx, SingleDesignView.tsx
  • Design service: src/lib/uploadScreenshots.ts
  • API client: src/api/generated

Notes

This refactoring was identified during the final sprint of the initial project. The current architecture works but has known technical debt. This issue captures the plan for addressing it when time permits.

Context from discussion: With 3 days remaining in the project, we prioritized moving component-level fetches into useDesign as a quick win, and documented this more comprehensive refactoring for later.

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions