View Model Introduction

November 17, 2025

View Model Introduction#

The term view model originates from the Model-View-ViewModel (MVVM) pattern. In practice, its greatest value lies in establishing a single "View Model" as the clear boundary between your application's state (business logic) and its view (React components).

Before we dive into the details of view model interfaces, let's take a step back and understand the general principles of view models.

What is a View Model?#

A view model is a type that describes the structure of the UI and the data that it displays. It is a type that is used to describe the state of the UI and the data that it displays. It is a type that is used to describe the actions that can be performed on the UI and the data that it displays.


General Principles#

1. Present Data as UI-Ready Strings

Every piece of data shown to the user should already be formatted when it reaches the UI.

  • Don’t expose raw types like Date or number directly; pre-format for display.
    • Bad: datePosted: Date
    • Good: postedAtDisplay: string
    • Bad: amount: number, currency: string, currencySide: "left" | "right"
    • Good: amountDisplay: string
      • For more complex UIs, you might use:
        amountDisplay: { pre: string, number: { muted: boolean, text: string }[], post: string }

2. Never Expose Raw IDs to the UI

  • Exposing IDs can lead the UI to leak business logic and is especially risky with AI-generated code.
    • Good: items: { key: string; onClick: () => void }[]
    • Bad: onClickItem(id: string), items: { id: string }[]

3. Let the View Model Structure Mirror the UI

  • Shape your View Model and its documentation so their hierarchy and fields directly describe what appears in the UI.
    • Bad:
      historyManagement: { draftChanges, timelineScrubber }
    • Good:
      topBar: { historyTimelineScrubber }, sidePanel: { draftChangeArea }
    • Tip: Use doc-comments to clarify any nuanced business behavior.

Practices That May Feel Wrong—But Are Worth It#

- Inline Reactivity Is Sometimes Best

Don’t arbitrarily split up components—often, it’s clearer to use an inline reactive renderer:

<Queryable queryable={vm.todos$}>
  {todos => /* this is a valid hook context */}
</Queryable>

- React Components on the View Model are OK (With a Caveat)

You can attach presentational React components (like icons or renderer functions) directly to your view model, so long as:

  • The business logic remains fully testable.
  • Those components are strictly for rendering/decoration.
icon: typeof Ta.Icon123;
renderDiff: (props) => React.ReactNode;

- Prefer Booleans Over Discriminated Unions Where Possible

Replace unions-with-functions for simple UI controls (buttons, dropdowns, etc.) with simple booleans or shape-specific fields:

  • Bad:
    Atom<{ enabled: true, click: () => void } | { enabled: false, disableDisplayReason: string }>

  • Good:

    • disabledAtom: Atom<null | { displayReason: string }>
    • click: () => void (always available, even if root-level event wiring is needed)
  • Bad:
    Atom<{ state: "open", keyDown: ("up" | "down") => void }>

  • Good:

    • options: []
    • keyDown: (key: "up" | "down") => boolean
      (returning boolean to signal event handling e.g. for stopping propagation)

- Keyboard/Event Handlers Should Return Booleans

Allow your keyboard or other event handlers to return a boolean indicating whether they've handled the event (to control propagation/default handling):

onKeyDown(event): boolean

- Add an Opaque _dev Field for Debugging

Add a field like _dev: Record<string, unknown> to your model for UI-side debugging tools.
Just ensure its structure remains opaque.