The Hook
Have you ever felt like your React components are doing too much? π Fetching data, managing loading/error state, formatting it, AND rendering the UI β all in one file.
In my project, SafeAnchor (a vertical SaaS for maritime fleet management), I decided to move away from "vibe coding" and commit to a proper architecture: MVVM (Model-View-ViewModel).
![SafeAnchor MVVM data flow: View, ViewModel, Service and API]

Why MVVM?
For a junior developer in 2026, understanding system architecture β not just syntax β is a must-have skill. Here's how I decoupled my frontend logic into three layers, straight from apps/frontend/src/ in the repo:
src/
βββ views/ # screens (JSX)
βββ viewmodels/ # screen state + logic (custom hooks)
βββ services/ # API communication
βββ models/ # data shape
1. Services β pure API logic
Services don't care about buttons or spinners. They only care about talking to the API and shaping the response. This is the actual getVesselById from services/vesselService.js:
export async function getVesselById(id) {
try {
const response = await fetch(`${apiUrl}/vessels/${id}`);
if (!response.ok) {
throw new Error("Nao foi possivel carregar a embarcaΓ§Γ£o.");
}
const data = await response.json();
return { vessel: data, source: "api", error: false };
} catch (error) {
const vessel = fallbackVessels.find((v) => v.id === Number(id));
return { vessel, source: "fallback", error: false };
}
}
Look closely at the catch: if the request fails, the service returns a hardcoded local vessel instead of throwing. That's a deliberate resilience decision β more on it below.
2. Hooks as ViewModels β the brain
All state management β loading, fetching, error handling β lives here. This is what keeps the UI clean. From viewmodels/useVesselDetailsViewModel.js:
export function useVesselDetailsViewModel(id) {
const [vessel, setVessel] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
async function loadVessel() {
try {
setIsLoading(true);
const loaded = await getVesselById(id);
setVessel(loaded.vessel);
} catch (error) {
setError("NΓ£o foi possΓvel carregar a embarcaΓ§Γ£o.");
} finally {
setIsLoading(false);
}
}
loadVessel();
}, [id]);
return { vessel, isLoading, error, removeVessel };
}
3. Clean Views β just props in, JSX out
views/VesselDetailsPage.jsx only receives data via the hook. No fetching, no logic β which makes it much faster to test and maintain:
export default function VesselDetailsPage() {
const { id } = useParams();
const viewModel = useVesselDetailsViewModel(id);
if (viewModel.isLoading) return <p>Carregando...</p>;
if (viewModel.error) return <p>{viewModel.error}</p>;
const vessel = viewModel.vessel;
return (
<main className="vessel-details">
<h1 className="vessel-details__title">EmbarcaΓ§Γ£o</h1>
<span className="vessel-details__value">{vessel.name}</span>
{/* ... */}
</main>
);
}
Put together, the data flows one way: API β Service β ViewModel (Hook) β View (JSX) β no shortcuts, no components secretly reaching into the network layer.
The Fallback That Made Me Rethink "MVP Code"
![Resilience fallback flow when the backend is offline]

The nicest surprise re-reading my own code: vesselService.js already falls back to an in-memory vessel array when the Express backend is unreachable. The ViewModel and the View don't know (or care) which path ran β they just get { vessel, source, error }. One try/catch buys the whole app the ability to stay demoable even with the backend down.
Where SafeAnchor Actually Stands
- β Epic 001 β Fleet Management Foundation: vessel listing, details, creation, editing, archiving β all shipped.
- π Epic 002 β Maintenance Management: up next.
- πΊοΈ Later: Safety Checklists (with QR-code field access), Document Management, Maritime Academy.
My Tech Stack & Learning Process
Right now it's React + Vite on the frontend and Express on the backend, with a documented target migration to Next.js + TypeScript + Redux Toolkit as the app grows into a feature-based structure. To speed up my learning, I use NotebookLM to organize scattered concepts into structured technical guides β basically an AI-powered tutor for engineering fundamentals.
What's Next?
- Refactoring CSS to the BEM standard
- Hardening the fallback/error paths ahead of Maintenance Management
- Building out Safety Checklists and QR-code field access
Let's Discuss! π
How do you handle state logic in your React apps? Do you prefer big all-in-one hooks, or a decentralized architecture like MVVM? Let me know in the comments!
Top comments (0)