State

State is the internal reactive data owned and managed exclusively by the component. Unlike props, state is not exposed or mapped to HTML attributes, and it is intended to store data that changes over the lifecycle of the component (e.g., UI toggle status, input values, API fetch results).

Declaring Initial State

To define a component's internal state, assign an object to the initialState class property. Each key in this object will be initialized as a reactive state getter.

javascript
1import { WebComponent } from '@beforesemicolon/web-component'2 3class CounterElement extends WebComponent {4    initialState = {5        count: 0,6        label: 'Clicks',7    }8}

Reading State in Templates

Like props, the properties of this.state are reactive getter functions. You can access the values inside your templates by passing the getter directly or by invoking it.

javascript
1import { WebComponent, html } from '@beforesemicolon/web-component'2 3class CounterElement extends WebComponent {4    initialState = {5        count: 0,6        label: 'Clicks',7    }8 9    render() {10        return html`11            <div>12                <span>${this.state.label}: ${this.state.count}</span>13            </div>14        `15    }16}

Modifying State via setState

To update state reactively, call this.setState(). This method accepts either a partial state object or a callback function.

Object Merge Format

You can pass a partial object containing the fields you want to update. WebComponent will merge the updates into the state automatically, so you do not need to manually spread the rest of the state.

javascript
1// Updates only the count; label remains untouched2this.setState({ count: 10 })

Callback Format

If your new state depends on the previous state, pass a callback function. The callback receives the current state object with its evaluated values (not getters) and must return a partial state object.

javascript
1this.setState((prev) => ({2    count: prev.count + 1,3}))

Lifecycle Restrictions

State updates trigger DOM updates. Because of this, calling this.setState() before the component is mounted (i.e. when this.mounted is false) or after it has been unmounted will result in an error.

javascript
1class MyComponent extends WebComponent {2    initialState = { data: null }3 4    constructor() {5        super()6        // ERROR: Cannot update state while component is unmounted.7        this.setState({ data: 'foo' })8    }9}

If you need to fetch data or trigger state updates as soon as the component loads, do so in the onMount lifecycle hook:

javascript
1class MyComponent extends WebComponent {2    initialState = { data: null }3 4    onMount() {5        fetch('/api/data')6            .then((res) => res.json())7            .then((data) => {8                if (!this.mounted) return9                this.setState({ data })10            })11    }12}

Derived UI Example

Keep the source state intact and derive display values inside functions.

javascript
1import { WebComponent, html, repeat } from '@beforesemicolon/web-component'2 3class SearchableList extends WebComponent {4    initialState = {5        query: '',6        items: ['Alpha', 'Beta', 'Gamma'],7    }8 9    results = () => {10        const query = this.state.query().toLowerCase()11        return this.state.items().filter((item) => {12            return item.toLowerCase().includes(query)13        })14    }15 16    render() {17        return html`18            <input19                value="${this.state.query}"20                oninput="${(event) =>21                    this.setState({ query: event.target.value })}"22            />23            <ul>24                ${repeat(25                    this.results,26                    (item) => html`<li>${item}</li>`,27                    () => html`<li>No matches.</li>`28                )}29            </ul>30        `31    }32}
edit this doc