Ramonda

@state

Marks a field as reactive. Read it while rendering and the component renders again whenever it is assigned.

The situation it is for

A search box. The typed text has to be remembered between renders, and changing it has to redraw what is on screen — that is what a @state field is:

import type { EventOn } from "@ramonda/core";

class Search extends Component {
  @state query = "";

  onType(e: EventOn<HTMLInputElement>) {
    this.query = e.currentTarget.value;
  }

  render() {
    return (
      <label>
        Search
        <input value={this.query} oninput={this.onType} />
      </label>
    );
  }
}

Two things happen because of the one decorator. this.query = … in the handler is a change, so the component renders again. And render() reading this.query is what ties it to the field — nothing declares that, and a render that stopped reading it would stop being woken by it.

There is no setter and no separate place for state to live. It is a field: read it with this.query, change it with this.query = "ada".

See State for the reactivity model this is the front door to.

Assignment is what fires

A signal fires when it is assigned, not when the value it holds changes inside. So this does nothing:

class Recent extends Component {
  @state queries: string[] = [];

  remember(query: string) {
    this.queries.push(query);
  }

  render() {
    return <p>{this.queries.length} searched</p>;
  }
}

and this does:

class Recent extends Component {
  @state queries: string[] = [];

  remember(query: string) {
    this.queries = [...this.queries, query];
  }

  render() {
    return <p>{this.queries.length} searched</p>;
  }
}

Mutating in place is reported both ways — as RMD005 when it runs, and by ramonda-check as state-mutated-in-place before it does.

What it refuses

Anything but a field. A getter that derives is @compute; a method is not state.

A write during a render. render() reads state; it does not write it. A write there schedules another render from inside a render, and is reported as RMD001 — with state-written-while-rendering finding it in the source, including through a helper the render calls three files away.

What it costs

One signal per field, and a comparison on assignment: writing the value it already holds re-renders nothing.

It is part of the hydration blob. A @state field is serialised so the browser resumes with the value the server had — which is why @persist beside it adds nothing and is reported as RMD046. A value that cannot be serialised is reported as unserializable-state.

Next

  • State — the model, and what a signal compares.
  • @compute — a value derived from state, cached.
  • @persist — for the fields @state does not cover.