Skip to content
Diagram showing a centralized state driving multiple UI steps through derived logic layers
King DJ30 December 20253 min read

Case Study: Building a State-Driven Step Form for Complex Decision Flows

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

avatar

King DJ

Building custom HubSpot CMS themes, HUBL modules, smart forms, and dynamic quote templates—optimized for speed, scalability, and CRM integration.

RELATED ARTICLES