DEV Community

Kun Shen
Kun Shen

Posted on

Three Frontend Patterns for Date Tools That Depend on Rules

Three Frontend Patterns for Date Tools That Depend on Rules

A normal date widget can add a duration and display the result. A policy-dependent date tool has a different job: it must show when the inputs are incomplete, which version of a rule was applied, and which conclusion the software cannot make.

Here are three TypeScript patterns that keep those boundaries visible.

1. Replace eligible: boolean with a state union

A Boolean makes every incomplete case look like a negative decision. Use a discriminated union instead:

type ResultState =
  | { kind: 'needs-route' }
  | { kind: 'needs-evidence'; fields: string[] }
  | { kind: 'estimate'; date: string; assumptions: string[] }
  | { kind: 'possible-issue'; checks: string[] }
  | { kind: 'outside-scope'; reasons: string[] };
Enter fullscreen mode Exit fullscreen mode

The component can now render a specific next step. More importantly, the domain layer cannot quietly convert missing information into false.

function Result({ state }: { state: ResultState }) {
  switch (state.kind) {
    case 'needs-route':
      return <Notice>Select the route before calculating a date.</Notice>;
    case 'needs-evidence':
      return <Notice>Check: {state.fields.join(', ')}</Notice>;
    case 'estimate':
      return <Estimate date={state.date} assumptions={state.assumptions} />;
    case 'possible-issue':
      return <Warning checks={state.checks} />;
    case 'outside-scope':
      return <Boundary reasons={state.reasons} />;
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Store rule versions with provenance

Avoid anonymous constants such as MAX_DAYS = 180. A policy rule needs an effective date, source, review date, and scope.

type RuleVersion<T> = {
  id: string;
  appliesFrom: string;
  appliesTo?: string;
  routes: string[];
  sourceUrl: string;
  reviewedAt: string;
  evaluate: (facts: T) => RuleCheck;
};
Enter fullscreen mode Exit fullscreen mode

When a rule changes, add a version. Do not replace the old object and silently change the meaning of stored results. Regression tests should cover a date on each side of the boundary.

it('selects the version active for the relevant period', () => {
  expect(selectRule('2026-01-01').id).toBe('rule-v2');
});
Enter fullscreen mode Exit fullscreen mode

3. Generate the explanation from the evaluation object

Do not calculate a date in one function and write a reassuring paragraph somewhere else. Return the information needed to explain the result:

type Evaluation = {
  state: ResultState;
  route: string;
  ruleIds: string[];
  sources: string[];
  inputWarnings: string[];
};
Enter fullscreen mode Exit fullscreen mode

The UI can show the route, rule versions, input warnings, and source links beside the date. If the result uses two historic branches, that should be visible without reading logs.

Preserve chronology and uncertainty

Travel, employment, or coverage records should remain event-level data. Do not discard the original entries after producing an annual total. Keep the evidence source and confidence level so corrections are recoverable.

type Interval = {
  start: string;
  end: string;
  source: 'document' | 'calendar' | 'memory';
  confidence: 'confirmed' | 'estimated';
};
Enter fullscreen mode Exit fullscreen mode

Normalise overlaps in a derived view, then test invariants: sorting does not change the total, duplicates do not double-count, and reversed dates fail early.

A public example

The ILR Calculator UK methodology and interface applies these ideas to settlement planning: route selection precedes the date, trip records stay in the browser, and the output is labelled as an estimate rather than an eligibility decision. The link is an implementation example, not a claim that the product or this post can assess an individual application.

These patterns are reusable in tax-residency tools, insurance waiting periods, employment-benefit calculators, and any frontend where rules change over time. The best result component is not the one that always produces a date. It is the one that can explain when a date is justified and when the software should stop.

Top comments (0)