Component Decomposition

MANDATORY pattern for frontend files exceeding 600 lines. Splits complex components into focused modules with clear responsibilities.

Split Pattern

ModuleContainsReact Imports
fooService.tsPure functions, constants, typesNone
useFooState.tsState, queries, mutations, handlersYes (custom hook)
FooSection.tsxPresentational UI (props + useTranslation)Yes (minimal)
Foo.tsxOrchestrator (calls hook, composes sections)Yes

Real Examples

File counts as of 2026-06:

DomainDirectoryFiles
Document Obtainingfrontend/src/components/document-obtaining/64
Form Builderfrontend/src/components/form-builder/30
Building Componentsfrontend/src/components/building-components-redesign/18
Invoicesfrontend/src/components/invoices/16

Typical Decomposition

components/invoices/
  InvoiceList.tsx           # Orchestrator
  useInvoiceListState.ts    # Queries, mutations, handlers
  InvoiceTable.tsx          # Presentational table
  InvoiceFilters.tsx        # Filter UI section
  invoiceService.ts         # Helpers, formatters, types

Component Library

Shared UI primitives live in frontend/src/components/ui/ (65 Shadcn/Radix components, 63 with stories, as of 2026-06). These are presentational by definition and do not need decomposition.

New components/ui/ components MUST have .stories.tsx files (CSF3, tags: ['autodocs']). The Storybook build is a blocking CI gate and stories run as Vitest story tests (PR#1958, PR#1966); story coverage is reported non-blocking.

Rules

  • Orchestrator (Foo.tsx): calls the custom hook, passes data down to sections. No direct API calls.
  • Custom hook (useFooState.ts): all React Query usage, mutations, and event handlers.
  • Sections (FooSection.tsx): receive props, render UI. Only hook allowed is useTranslation.
  • Service (fooService.ts): zero React imports. Pure TypeScript functions and types.

See Also