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
- Single Responsibility Principle - Each hook should have one clear purpose
- Easier Testing - Isolated concerns are easier to unit test
- Better Reusability - Screenshot logic could be used for other features
- 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:
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:
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:
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:
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;
}
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:
Success Criteria:
- All existing tests pass
- New hooks have test coverage
- Architecture is documented
Testing Strategy
Before Refactoring:
During Refactoring:
After Refactoring:
Migration Path
This refactoring can be done incrementally:
- Phase 1: Extract
useScreenshots (doesn't affect other code much)
- Phase 2: Create
useDesignData (can coexist with old code)
- Phase 3: Extract serialization (low risk, pure functions)
- Phase 4: Refactor
useDesign (final integration)
- 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
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.
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 logicuseDesign- Handles CRUD operations + screenshot uploads + some state managementProblem:
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
Proposed Architecture
New Hook Structure
Implementation Steps
Step 1: Extract Screenshot Logic (1-2 hours)
Goal: Create
useScreenshotshook to isolate all screenshot-related operations.Tasks:
hooks/useScreenshots.tsuseDesigntouseScreenshotsuploadScreenshots(designId, fullBlob, thumbnailBlob)→ returns{ screenshotUrl, thumbnailUrl }captureScreenshot()→ captures from canvas (if needed)useDesignto use the newuseScreenshotshookFiles to modify:
src/hooks/useScreenshots.tssrc/hooks/designs/useDesign.tsSuccess Criteria:
useDesignno longer contains screenshot upload codeStep 2: Create Pure CRUD Hook (2-3 hours)
Goal: Create
useDesignDatahook for all database operations.Tasks:
hooks/useDesignData.tsDesignServiceAPI calls from components anduseDesigngetAllDesigns()→ returnsDesignDto[]getDesignById(id)→ returnsDesignDtocreateDesign(data)→ returnsDesignDtoupdateDesign(id, data)→ returnsDesignDtodeleteDesign(id)→ returnsvoidupdateDesignName(id, name)→ returnsDesignDtodesigns,currentDesign,isLoading,erroruseDesignDatainstead of directDesignServicecallsFiles to modify:
src/hooks/useDesignData.tssrc/components/UserDesigns.tsxsrc/components/SingleDesignView.tsxsrc/hooks/designs/useDesign.tsSuccess Criteria:
DesignServicecalls in componentsuseDesignDataStep 3: Extract Serialization Logic (1 hour)
Goal: Separate serialization from UI state management.
Tasks:
lib/designSerializer.ts(pure functions, not a hook)useCustomDesign:serializeDesign(customDesign)→ returns JSON string (wasgetSceneData)deserializeDesign(jsonString)→ returnsCustomDesignobject (wasloadSceneData)useCustomDesignto use these functionsgetSceneDataandloadSceneDatafromuseCustomDesignFiles to modify:
src/lib/designSerializer.tssrc/hooks/designs/useCustomDesign.tsgetSceneDataorloadSceneDataSuccess Criteria:
useCustomDesignonly manages UI stateStep 4: Refactor useDesign as Orchestrator (1-2 hours)
Goal: Transform
useDesigninto a high-level orchestrator that composes other hooks.Current responsibilities:
New responsibilities:
useDesignData+useScreenshots+useCustomDesignsaveDesignWithScreenshot()Tasks:
useDesignData,useScreenshotsFiles to modify:
src/hooks/designs/useDesign.tsSuccess Criteria:
useDesigndelegates to specialized hooksDesignServiceor screenshot upload code inuseDesignStep 5: Testing & Documentation (1-2 hours)
Tasks:
designSerializer.tsuseDesignData(mock API calls)useScreenshots(mock upload)Success Criteria:
Testing Strategy
Before Refactoring:
During Refactoring:
After Refactoring:
Migration Path
This refactoring can be done incrementally:
useScreenshots(doesn't affect other code much)useDesignData(can coexist with old code)useDesign(final integration)Each phase can be a separate PR for easier review.
Potential Issues
Issue: Breaking Changes
Risk: Components rely on current
useDesigninterfaceMitigation: Keep same public API initially, only change internals
Issue: State Management Conflicts
Risk: Multiple hooks managing similar state
Mitigation: Make
useDesignDatathe single source of truth for design dataIssue: Performance
Risk: More hooks = more re-renders?
Mitigation: Careful use of
useMemo,useCallback, and ensure hooks don't cause unnecessary rendersDefinition of Done
DesignServicecalls in componentsEstimated Time: 6-10 hours
References
src/hooks/designs/useDesign.ts,src/hooks/designs/useCustomDesign.tsUserDesigns.tsx,SingleDesignView.tsxsrc/lib/uploadScreenshots.tssrc/api/generatedNotes
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
useDesignas a quick win, and documented this more comprehensive refactoring for later.