Products · Build a dot

Pages and state

Hold a page's data in state, change it with mutations and actions, and run code as the page loads, shows, hides and refreshes.

Each page has a controller, controller.ts. It holds the page's state (the data its view shows) and the code that changes it: mutations, actions and hooks.

This page shows an EDSA meter's balance and lets the person top it up. It loads the balance as it opens:

pages/meter/controller.ts
import state from './state';

export default definePage({
  initialState: state,
  hooks: {
    async onLoad(options) {
      this.setState({ meter: options.meter ?? '' });
      await this.actions.load();
    },
  },
  mutations: {
    setAmount: (_state, event: ValueEvent<number | null>) => ({
      amount: event.detail.value,
    }),
  },
  actions: {
    async load(state) {
      if (!state.meter) {
        this.setState({ error: 'Go back and choose a meter.' });
        return;
      }
      this.setState({ loading: true, error: '' });
      try {
        const response = await this.dash.network.http.request<{ balance: number }>({
          url: `${env.API_URL}/meters/${state.meter}`,
          as: 'json',
        });
        if (!response.ok) {
          throw new Error();
        }
        this.setState({ balance: response.body.balance });
      } catch {
        this.setState({ error: "We couldn't load your meter." });
      } finally {
        this.setState({ loading: false });
      }
    },
    confirm(state) {
      this.navigation.push('confirm', { meter: state.meter, amount: String(state.amount) });
    },
  },
});
pages/meter/state.ts
export default {
  meter: '',
  balance: 0,
  amount: null as number | null,
  loading: false,
  error: '',
};

The view shows a placeholder while it loads, and a way to try again if it fails:

pages/meter/view.dml
<View>
  <If condition="{{ loading }}">
    <Skeleton shape="text" lines="2" />
  </If>
  <ElseIf condition="{{ error }}">
    <Notice tone="danger" title="Not loaded">{{ error }}</Notice>
    <Button variant="secondary" onClick="load">Try again</Button>
  </ElseIf>
  <Else>
    <Text variant="display" numeric>{{ balance }} kWh</Text>
    <AmountInput label="Top-up amount" value="{{ amount }}" onChange="setAmount" />
    <Button onClick="confirm" disabled="{{ !amount }}">Top up</Button>
  </Else>
</View>

State

Keep state to plain data: strings, numbers, booleans, null, arrays and objects. A Date or Map won't work in state, so store dates as text: '2026-10-24'.

Dot checks your code against the types in initialState. For a value that starts empty, give its type, as amount: null as number | null does above.

Never change state in place, like pushing onto an array. Set a new value. Each key replaces the old value whole, so to change one field of an object, pass the whole new object:

TypeScript
this.setState((current) => ({ profile: { ...current.profile, name: 'Aminata' } }));

Mutations

A mutation is a quick function. It gets the state and the event, and returns only the keys that change, like setAmount above. It can't wait or call Dash, which keeps it easy to read and test.

Actions

An action does slower work. It can call your server or Dash, wait for the answer and open other pages. It changes state with this.setState(), like load above.

In actions and hooks, this also has:

  • this.state, the state right now
  • this.mutations and this.actions, which is how onLoad runs load
  • this.dash, to call Dash
  • this.captureView(), to make a picture of a View

Actions also have this.navigation, to open other pages. Hooks don't have it.

The Pages and dot reference lists the rest, including the setters for Dash's bars and page colors.

Page hooks

HookWhen it runs
onLoad(options)The page opens, with its launch options
onShow()The page comes back to the top, or the person comes back to Dash
onHide()Another page covers it, or the person leaves Dash
onUnload()The page closes
onRefresh()The person pulls the page down to refresh it. A spinner shows until the hook finishes. Without this hook, the page can't be pulled

Hooks run one at a time, so onShow sees what onLoad loaded.

Errors

When an action or hook throws and nothing catches it, the person sees nothing. Catch the errors you expect and put a message in state, as load does. For the codes Dash calls fail with, see Handle errors.

For the errors you didn't expect, add an onError(error) hook to defineDot in dot.ts, and send them to your error tracker.

In the simulator, an error nothing catches shows as a toast over the phone and a [crash] line in the browser's console, so you notice it while you build.

Keep reading