DEV Community

Cover image for React + MVVM: The Entity I Didn't Create (and the State I Didn't Store)
luis-botelho
luis-botelho

Posted on

React + MVVM: The Entity I Didn't Create (and the State I Didn't Store)

The Hook

Most architecture posts are about what you build. This one is about what I didn't build — and why skipping it was the actual engineering decision.

Epic 003 — Safety Checklists just shipped as v0.3.0 for SafeAnchor. Three complete modules now: Fleet, Maintenance, Safety Checklists — this is Part 2 of the series, picking up after Part 1's MVVM foundation. (v0.2.0/Maintenance shipped in between without its own post — quick recap below before we get to the interesting part.)

Quick recap of v0.2.0 (Maintenance Management): the same MVVM shape repeated for a second domain, the project's first real relationship (Vessel 1:N Maintenance), and its first aggregate endpoint — a dashboard returning { total, preventive, corrective, pending, completed } instead of a list, split into its own maintenanceDashboardService for a clean Single Responsibility boundary.

The Entity I Didn't Create

The checklist module needed templates, executions, and an inspection history per vessel. The "obvious" way to build history is a new Inspection entity: its own model, controller, service, maybe its own table down the line.

That's not what's in the repo. GET /vessels/:id/inspections points straight at the controller that already serves checklist executions:

js
// backend/src/routes/vesselRoutes.js
router.get("/:id/inspections", getChecklistExecutionsByVesselIdController);

And the "inspection history" logic is just a filter + sort on ChecklistExecution:

js
// backend/src/services/checklistExecutionService.js
export function getChecklistExecutionsByVesselId(vesselId) {
return checklistExecutions
.filter((execution) => execution.vesselId === vesselId)
.sort((a, b) => new Date(b.executedAt) - new Date(a.executedAt));
}

There's no inspectionModel.js, no inspectionController.js. "Inspection" is a name the product uses — under the hood it's the same ChecklistExecution record, viewed through a vessel-scoped, sorted lens. One fewer entity, one fewer set of endpoints, one fewer thing that can drift out of sync with the rest of the domain.

The State I Didn't Store

The inspection history screen needed sortable results — ascending or descending by date — without an extra fetch. The easy-but-wrong move is a second useState for the sorted list, kept in sync with a useEffect. Instead:

js
// viewmodels/useInspectionHistoryViewModel.js
export function useInspectionHistoryViewModel(vesselId) {
const [inspections, setInspections] = useState([]);
const [sortOrder, setSortOrder] = useState("desc");

// ...fetch logic sets inspections...

const orderedInspections = [...inspections].sort((a, b) => {
if (sortOrder === "desc") {
return new Date(b.executedAt) - new Date(a.executedAt);
}
return new Date(a.executedAt) - new Date(b.executedAt);
});

return { inspections, isLoading, error, orderedInspections, sortOrder, setSortOrder };
}

orderedInspections is recomputed on every render from state that already exists. No second useState, no synchronization useEffect, no way for the sorted list to disagree with the source data. If a value can be derived from state you already have, it isn't state.

What Shipped in v0.3.0
✅ Checklist Templates — reusable models, multiple items per template
✅ Checklist Execution — recorded against a specific vessel
✅ Inspection History — chronological, sortable, reusing ChecklistExecution
✅ Same MVVM shape (View → ViewModel → Service → Backend) as modules 1 and 2
Debt We're Naming on Purpose

Not everything gets solved by reuse. Still on the list: a dedicated preventive-maintenance history screen, a shared/reusable history component across modules (right now each one is its own page), pagination and filters, and — the big one — moving off in-memory mock data onto PostgreSQL + Prisma.

What's Next

Documentation Management — file storage instead of structured records, a genuinely different kind of problem from the last three epics.

Let's Discuss! 👇

Where's the line for you between "this deserves its own entity" and "this is just a view over something that already exists"? I'd like to hear where it's bitten people either way.

SafeAnchor #WebDev #React #Architecture #LearningInPublic #SoftwareEngineering

Top comments (0)