Skip to content

State Management

As a user moves through a questionnaire, their answers need to be stored, validated, and used to determine the next step. Birdhouse handles this through two layers: the qnr-framework state manager (in-memory, per-questionnaire) and the app’s Redux store (persistent, per-vertical).

The useStateManager hook from qnr-framework provides the core answer-tracking logic. It manages a simple key-value store where:

  • Key = questionId (e.g., dateOfBirth, plan, coverageStartDate)
  • Value = the user’s answer (string, number, object, array — depends on question type)

When the user answers a question, the flow is:

flowchart LR
    A["User submits<br/>answer"] --> B["updateState()<br/>stores answer"]
    B --> C["autoRemove<br/>clears dependent<br/>answers"]
    C --> D["Rules evaluated<br/>to find next<br/>question"]
    D --> E["Next question<br/>rendered"]

The app wraps the qnr-framework state manager with a Redux reducer (genericQuestionnaire). This provides:

  • Persistence: Answers survive page refreshes and browser navigation
  • Per-vertical isolation: Each vertical’s answers are stored separately
  • Cross-component access: Other parts of the app (checkout, processing, analytics) can read the answers

When a user changes an earlier answer, downstream answers that depended on it may no longer be valid. The autoRemove behavior handles this automatically.

Example: A user selects “Employed” for employment status, then answers salary questions. If they go back and change to “Student”, the salary answers are automatically removed because they no longer apply.

This is configured through removeAnswersLogic — a per-question configuration that specifies which answers should be cleared when a particular answer changes:

Config Meaning
removeAnswersLogic: { employmentStatus: ['salary', 'employer'] } When employmentStatus changes, remove salary and employer answers

Without this, stale answers would persist and could cause incorrect pricing or confusing pre-filled values if the user navigates back and forth.

This behavior removes answers for questions that are not marked as required. It is useful when the question list changes dynamically — for example, when a rule skips over optional questions. Non-required questions that the user never saw should not have stale answers in the state.

The isComplete() function checks whether all required questions have been answered. This determines:

  • Whether the user can proceed to checkout
  • Whether a progress indicator shows “complete”
  • Whether the questionnaire state is valid for submission

It works by iterating through all questions marked as required: true and checking if an answer exists in the state for each one.

flowchart TD
    A["User answers question"] --> B["onAnswer callback fires"]
    B --> C["stateManagerHelper.updateState()"]
    C --> D["Answer stored in state<br/>{questionId: answer}"]
    D --> E["autoRemove runs:<br/>clears dependent answers<br/>based on removeAnswersLogic"]
    E --> F["Redux action dispatched:<br/>stores in persistent store"]
    F --> G["Rules engine evaluates<br/>to determine next question"]
    G --> H["isComplete() checked<br/>to update progress"]

The answer format depends on the question type:

Question type Answer format Example
Radio / Select String "PREMIUM"
Text / Email String "user@example.com"
Date String (ISO) "1990-05-15"
Number / Currency Number 45000
Boolean / Toggle Boolean true
Address Object { street: "...", city: "...", postalCode: "..." }
Multi-select Array ["GLASS", "BICYCLE"]
Quote (plan selection) Object { plan: "PREMIUM", addons: [...], options: {...} }

When a user goes back to a previous question, their earlier answer is still in state and pre-fills the input. If they change it, autoRemove cleans up any dependent answers.

Because the Redux store persists (via local storage or session storage), users do not lose their progress on page refresh. The questionnaire resumes from where they left off.

Each vertical’s answers are isolated in the Redux store. Starting a household questionnaire does not affect answers from a previous dental questionnaire.

If a questionnaire behaves unexpectedly (wrong question shown, incorrect pricing, incomplete progress), the first thing to check is the answer state. In the browser’s Redux DevTools, look at the genericQuestionnaire slice to see exactly what answers are stored and whether any stale values are present.