Analytics & Telemetry

Form Analytics & Telemetry

The core package (@formosaic/core) provides an opt-in analytics system for tracking form lifecycle events. When analytics callbacks are provided in form settings, events such as field focus, blur, change, validation errors, form submission, abandonment, wizard step navigation, and rule evaluation are automatically tracked.


Setup

Add an analytics object to IFormSettings inside your IFormConfig. Each callback is optional -- provide only the events you care about.

import { IFormConfig } from "@formosaic/core";

const formConfig: IFormConfig = {
  version: 2,
  fields: {
    name: { type: "Textbox", label: "Name", required: true },
    email: { type: "Textbox", label: "Email" },
  },
  settings: {
    analytics: {
      onFieldFocus: (fieldName) => console.log("Focus:", fieldName),
      onFieldBlur: (fieldName, timeSpentMs) => console.log("Blur:", fieldName, timeSpentMs + "ms"),
      onFieldChange: (fieldName, oldValue, newValue) => telemetry.track("field_change", { fieldName }),
      onValidationError: (fieldName, errors) => telemetry.track("validation_error", { fieldName, errors }),
      onFormSubmit: (values, durationMs) => telemetry.track("form_submit", { durationMs }),
      onFormAbandonment: (filledFields, emptyRequired) => telemetry.track("form_abandoned", { emptyRequired }),
      onWizardStepChange: (from, to) => telemetry.track("wizard_step", { from, to }),
      onRuleTriggered: (event) => telemetry.track("rule_triggered", event),
    },
  },
};

IAnalyticsCallbacks Interface

All callbacks are optional. If a callback is not provided, the corresponding event is silently ignored with zero overhead.

CallbackSignatureDescription
onFieldFocus(fieldName: string) => voidField receives focus
onFieldBlur(fieldName: string, timeSpentMs: number) => voidField loses focus (with duration)
onFieldChange(fieldName: string, oldValue: unknown, newValue: unknown) => voidFires once per actual value change (not per re-render)
onValidationError(fieldName: string, errors: string[]) => voidFires once when a field's error appears or its message changes — not on every re-render while the error persists
onFormSubmit(values: Record<string, unknown>, durationMs: number) => voidForm submitted successfully
onFormAbandonment(filledFields: string[], emptyRequiredFields: string[]) => voidUser navigates away with unsaved changes
onWizardStepChange(fromStep: number, toStep: number) => voidWizard step changes
onRuleTriggered(event: { fieldName: string; ruleIndex: number; conditionMet: boolean }) => voidRule evaluated

useFormAnalytics Hook

import { useFormAnalytics, IFormAnalytics } from "@formosaic/core";

const analytics: IFormAnalytics = useFormAnalytics(callbacks);

The hook returns a stable, memoized object with tracking methods: trackFieldFocus, trackFieldBlur, trackFieldChange, trackValidationError, trackFormSubmit, trackFormAbandonment, trackWizardStepChange, trackRuleTriggered.


Integration Example: Application Insights

import { appInsights } from "./telemetryClient";
import { IAnalyticsCallbacks } from "@formosaic/core";

const analyticsCallbacks: IAnalyticsCallbacks = {
  onFieldFocus: (fieldName) => {
    appInsights.trackEvent({ name: "FormFieldFocus", properties: { fieldName } });
  },
  onFieldBlur: (fieldName, timeSpentMs) => {
    appInsights.trackMetric({
      name: "FormFieldTimeSpent",
      average: timeSpentMs,
      properties: { fieldName },
    });
  },
  onFormSubmit: (values, durationMs) => {
    appInsights.trackEvent({
      name: "FormSubmit",
      measurements: { durationMs },
    });
  },
};

Design Decisions

  • Opt-in with zero overhead: When settings.analytics is not provided, every tracking function short-circuits via optional chaining.
  • Ref pattern for callback stability: The callbacks object is stored in a useRef, so the returned IFormAnalytics object reference is stable across re-renders.
  • Focus timing via useRef map: Per-field focus duration is tracked with a useRef<Record<string, number>> map.
  • Form duration from hook mount time: formStartTime is captured once at mount for durationMs calculations.