PraxisJS

Watchers

React to state changes with @Watch, @When, and @Until — declarative side-effect handlers and async waiters for reactive properties.

Watchers

Watcher decorators let you react to state changes declaratively, without setting up effects manually in onMount.


@Watch(...propNames)

Calls the decorated method whenever the watched property changes. The method receives the new value and old value as arguments.

import { Component, State, Watch } from '@praxisjs/decorators'
import { WatchVal } from '@praxisjs/decorators'

@Component()
class Search extends StatefulComponent {
  @State() query = ''

  @Watch('query')
  onQueryChange(newVal: WatchVal<this, 'query'>, oldVal: WatchVal<this, 'query'>) {
    console.log(`query: "${oldVal}" → "${newVal}"`)
    this.fetchResults(newVal)
  }

  async fetchResults(q: string) { /* ... */ }

  render() {
    return (
      <input
        value={() => this.query}
        onInput={(e) => { this.query = (e.target as HTMLInputElement).value }}
      />
    )
  }
}

Watch multiple properties

When watching multiple props, the method receives an object with all current values:

import { WatchVals } from '@praxisjs/decorators'

@Component()
class Form extends StatefulComponent {
  @State() firstName = ''
  @State() lastName = ''

  @Watch('firstName', 'lastName')
  onNameChange(vals: WatchVals<this, 'firstName' | 'lastName'>) {
    console.log(`Name: ${vals.firstName} ${vals.lastName}`)
  }
}

Coalesced updates

When multiple watched props change in the same synchronous block, the callback fires once with all final values — not once per changed prop. Changes made to signals inside the callback are also batched automatically.

this.firstName = 'Jane'
this.lastName = 'Smith'
// → onNameChange fires once with { firstName: 'Jane', lastName: 'Smith' }
Storybook
Live demo — @Watch single prop
Storybook
Live demo — @Watch multi-prop coalescing

@When(propName)

Calls the decorated method exactly once, the first time the named property becomes truthy. Automatically set up on mount and cleaned up on unmount.

@Component()
class DataLoader extends StatefulComponent {
  @State() data: string[] | null = null

  @When('data')
  onFirstLoad() {
    console.log('Data arrived for the first time:', this.data)
    // safe to use this.data here — it's truthy
  }

  render() {
    return () => this.data
      ? <ul>{() => this.data!.map(d => <li>{d}</li>)}</ul>
      : <p>Loading...</p>
  }
}

Use @When for one-time initialization that depends on a value arriving — like the first API response, or a user becoming authenticated.

Storybook
Live demo — @When

@Until(propName)

Replaces the decorated method with one that returns a Promise resolving to the first truthy value of the named property. Each call to the method returns a fresh promise.

import { Component, State, Until } from '@praxisjs/decorators'

@Component()
class UserProfile extends StatefulComponent {
  @State() user: User | null = null

  @Until('user')
  waitForUser(): Promise<User> { return Promise.resolve(null!) }
  // The method body is replaced entirely — it's just a type hint.

  async loadProfile() {
    const user = await this.waitForUser()
    console.log('User is ready:', user.name)
  }

  render() { /* ... */ }
}

If the property is already truthy when the method is called, the promise resolves on the next microtask.

Use @Until when downstream code needs to await a reactive value rather than react to it with a side effect. If you're showing/hiding UI based on the value, use @When or @Watch instead.

Storybook
Live demo — @Until

On this page