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 Problem I Was Solving
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
Core Design Principle
State Is the Single Source of Truth
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.
High-Level Architecture
Conceptual Diagram: Data Flow
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.
Step Form ≠ Multi-Page Form
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.
Why This Matters
-
Steps can be inserted or removed without refactoring
-
Optional flows don’t break the system
-
Back-navigation is safe by default
Rendering Strategy
Render From State, Not Events
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
Derived Helpers (Read-Only 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
Summary & Grouping Logic
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
Conceptual Diagram: Summary Generation
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
Handling Change (The Real Test)
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.
Error Prevention by Design
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
Engineering Outcomes
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
What This Project Demonstrates
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.
Final Reflection
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

