Don't forget 'this' when updating Alpine state

I recently had to implement an auto-save feature for a text editor (this one, in fact!), and ran into a slightly-surprising limitation in Alpine's state management. The problem arises when you need to get or set the state of a model from outside the model object, such as from inside a setInterval call.

Here is some code to demonstrate what doesn't work:

<div x-model="state()">
    <div x-text="count"></div>
</div>
function state() {
    const api = {
        count: 0
    }
    setInterval(() => api.count++, 1000)
    return api
}

I had naively expected something like this to work because I am referencing the same object that is returned from state and presumably is used by Alpine to sync the UI.

As it turns out, however, they do some fancy proxying that means the object you give to x-model is only the starting point for your model. All subsequent updates must be applied to the underlying context object, aka this.

Here is how you make that pattern work correctly:

<div x-model="state()" x-init="init()">
    <div x-text="count"></div>
</div>
function state() {
    const api = {
        count: 0,
        init() {
            setInterval(() => this.count++, 1000)
        }
    }
    return api
}

x-init is a handy hook for running some code when a component (an element with x-model) is initialized. In this case, we simply start our interval here and have direct access to this.