This case study documents how I designed and built a state-driven, multi-step discovery tool for a domain with high decision complexity and strict clarity requirements.
The goal was not visual polish.
The goal was predictability, extensibility, and user confidence —both in the UI and in the codebase.
This article explains:
Why common form architectures fail at scale
The architectural decisions I made
How state, rendering, and summaries are derived
What makes the system maintainable long-term
The domain required users to make many interdependent decisions:
Some options unlock others
Some choices invalidate previous ones
Users often change their mind mid-flow
Final output must be accurate and explainable
Traditional approaches break down because they rely on:
DOM-driven conditionals
Hardcoded step logic
UI components holding their own state
Copy-pasted pricing and summary logic
This leads to:
Inconsistent UI
Fragile updates
Difficult debugging
High regression risk
Everything in the system is derived from a single, central state object.
UI does not store truth
DOM does not control logic
No duplicated derived values
The UI becomes a pure projection of state.
src/
core/
state.js // Central single source of truth
actions.js // Controlled state mutations
selectors.js // Derived read-only helpers
constants.js // Static enums & configuration
render/
renderEngine.js // Orchestrates rendering from state
renderSteps.js // Step-level rendering
renderSummary.js // Summary generation from state
steps/
bedrooms/
index.js // Step entry
options.js // Option definitions
rules.js // Availability logic
layout/
index.js
options.js
rules.js
components/
SelectionGroup.jsx
OptionCard.jsx
SummaryGroup.jsx
utils/
normalize.js
escapeHtml.js
safeNumber.js
styles/
steps.css
summary.css
utilities.css
No component mutates state directly.
No UI logic bypasses state.
I intentionally avoided a “page-based” mindset.
Instead, each step is:
State-aware
Conditionally rendered
Order-independent
Steps exist as logical units, not navigation pages.
Steps can be inserted or removed without refactoring
Optional flows don’t break the system
Back-navigation is safe by default
Bad pattern:
onClick
→ hide UI section
→ show another section
→ manually update summary
Chosen pattern:
onClick
→ update central state
→ re-render everything from state
This eliminates:
UI drift
Orphaned selections
Manual cleanup logic
All decision logic lives in pure helper functions:
function isBedroomSelected() {
return Boolean(state.bedrooms);
}
function isStudyAvailable() {
return state.bedrooms >= 3;
}
function shouldRenderUpgradeGroup(group) {
return getSelectedUpgrades(group).length > 0;
}
function getSelectedSummaryByGroup() {
return GROUP_ORDER.map(group => ({
label: GROUP_LABELS[group],
items: getSelectedUpgrades(group)
}));
}
Rules:
Helpers never mutate state
Helpers never touch the DOM
Helpers can be reused anywhere
This makes logic:
Testable
Predictable
Easy to extend
One of the hardest problems was accurate summarisation.
Users need to trust that:
What they see is what they chose
Grouping always makes sense
Order is consistent
State
│
├── bedrooms
├── layout
├── upgrades[]
│
▼
Normalize → Group → Sort → Render
Summaries are derived, never manually assembled.
This ensures:
No duplication
No mismatch between UI and output
One logic path for screen, PDF, or export
Users change their minds. A lot.
Because everything is state-driven:
Changing one answer re-evaluates dependencies
Invalid selections are automatically dropped
UI updates safely without special-case code
No rollback logic.
No “reset this field” hacks.
Instead of validating after mistakes, the system:
Prevents invalid states
Hides impossible options
Guides users without blocking them
This reduces:
Edge cases
Support issues
User frustration
From a developer perspective, this architecture delivers:
Clear data flow
Low coupling
Easy debugging
Safe refactors
Confidence adding new steps or logic
From a UX perspective:
Reduced cognitive load
Clear progress
Trust in the system
This case study highlights my strengths in:
State-driven UI architecture
Complex decision modelling
UX logic beyond visuals
Maintainable, scalable frontend systems
Production-focused thinking
This is not demo code.
This is engineering for change.
Forms are easy.
Decision engines are not.
This project shows how discipline in state management and clear separation of concerns can turn a complex, high-risk UX into a system that feels simple—for both users and developers . View Project