Skip to main content

Hosanna Fundamentals

Hosanna screens and components are TypeScript classes whose state is reconciled into platform-rendered views. The essential contract is @view + BaseView + @state + getViews().

A Minimal View

The following shape matches current views in hosanna-ui-samples-public:

// The generated file exists after `npm run generate` or `npx hst generate:all`.
import {
WelcomeCard,
WelcomeCardViewStruct,
} from '@hs-generated/app/WelcomeCard-generated-struct';
import type {
WelcomeCardState,
} from '@hs-generated/app/WelcomeCard-generated-struct';
export { WelcomeCard, WelcomeCardViewStruct };

import { state, view } from '@hs-src/hosanna-ui/lib/decorators';
import { BaseView } from '@hs-src/hosanna-ui/views/lib/BaseView';
import {
ViewState,
ViewStruct,
} from '@hs-src/hosanna-ui/views/lib/view-api';
import { Button } from '@hs-src/hosanna-ui/views/controls/Button';
import { VGroup } from '@hs-src/hosanna-ui/views/groups/VGroup';
import { Label } from '@hs-src/hosanna-ui/views/primitives/Label';

@view('WelcomeCard')
export class WelcomeCardView extends BaseView<WelcomeCardState> {
@state title = 'Welcome';
@state activationCount = 0;

protected override getViews(): ViewStruct<ViewState>[] {
return [
VGroup([
Label({ text: this.title }),
Button({ text: 'Continue' })
.isInitialFocus()
.onClick(() => {
this.activationCount++;
this.title = `Activated ${this.activationCount} times`;
}),
]).itemSpacing(24),
];
}
}

The generated import path depends on the source file's path beneath the generator root. Do not copy the illustrative @hs-generated/app/... path without adapting it to your project.

What Each Part Does

  • @view('WelcomeCard') registers the class name used by generation and reflection.
  • BaseView<WelcomeCardState> supplies lifecycle, reconciliation, focus, layout, child lookup, and renderer ownership.
  • @state creates a reactive field. Assigning it schedules the view to be reconciled.
  • getViews() returns an array of ViewStruct descriptions. It does not directly create DOM, UIKit, Android, or SceneGraph elements.
  • WelcomeCard({...}) is the generated factory other views use to include this component.

State and Inputs

Generated state fields serve two roles:

  • They are inputs when a parent calls WelcomeCard({ title: 'Browse' }).
  • They are reactive fields when the view assigns this.title = 'Playing'.

Do not add a constructor merely to receive component props. Use @state fields and the generated factory. Keep transient collaborators, timers, and other non-render data as normal class fields.

Rendering Rules

  • Keep getViews() deterministic for the current state.
  • Do not assign state, start requests, or perform navigation inside getViews().
  • Use arrow functions for event handlers that need this.
  • Give interactive or later-accessed views stable IDs.
  • Use the concrete view class—not its factory function—as a lookup generic, such as getSubView<LabelView>('title').
  • Clean up timers, observations, or native resources in the appropriate lifecycle hook.

Generate and Validate

Generation is part of normal development:

npm run generate
npm test

In the sample repository, npm test, npm run build, and hst run perform generation as part of their workflow. If a direct TypeScript or Vitest command cannot resolve @hs-generated, run the repository's generation script first.

Continue with Views, SubViews, and Children and Core and Native Components.

Talk to us