State

Something that is truly missing in web APIs is reactivity, the ability to react to changes instead of listening to events. A big promise is the tc39 proposal which is attempting to bring signals to JavaScript.

Until then, Markup exposes a standalone API that gives you that capability called state.

The state API can be used with or without templates allowing you to create state stores, enhance Web Components, and even provide reactivity with raw DOM manipulation.

javascript
                const [count, setCount] = state(0)

const btn = document.createElement('button')
btn.type = 'button'

btn.addEventListener('click', () => {
    setCount((prev) => prev + 1)
})

effect(() => {
    btn.textContent = `count: ${count()}`
})

document.body.append(btn)
            

The best part is when you combine state and templates to render DOM nodes that react to updates as they happen without having to manipulate the DOM yourself.

javascript
                const handleClick = () => {
    setCount((prev) => prev + 1)
}

html`<button type="button" onclick="${handleClick}">count: ${count}</button>`
            

Markup templates do not traverse the DOM to check for updates. Instead, it creates render effects in place to update the DOM exactly where and when needed.

Input

To initialize a state, you can optionally provide an initialValue as well as a StateSubscriber which is a function that will be called every time the state changes.

typescript
                const [count] = state<number>(0, () => {
    // react to change
})
            

When no input value provided, the default value is an empty string.

Return

The state function will return and array with three functions, a StateGetter, StateSetter, and a StateUnSubscriber.

javascript
                const [count, updateCount, unsubscribe] = state(0, () => {
    // will only get called once
})

updateCount(10)

unsubscribe()
            

StateGetter

The StateGetter is a function you must call to get the current value of the state.

javascript
                const [count] = state(0)

console.log(count()) // logs 0
            

StateSetter

The StateSetter is a function you call with the new value for the state or a function that gets called with the current value and must return a new value.

javascript
                const [count, updateCount] = state(0)

// provide a new value
updateCount(10)

// use current value and perform a calculation
updateCount(count() + 5)

// use the callback to update the value
updateCount((prev) => prev + 5)
            

Calling the StateSetter with same value will not cause the subscribers to be called. A shallow comparison is made before updating the current value which prevents unnecessary updates.

javascript
                const [count, updateCount] = state(0, () => {
    // this will never get called
    // given the setInterval update bellow
    console.log(count())
})

setInterval(() => {
    updateCount(0)
}, 1000)
            

StateUnSubscriber

In case you provide a StateSubscriber as a second argument for the state, you can then use the StateUnSubscriber to stop listening to the state changes.

javascript
                const [count, updateCount, unsubscribeFromCount] = state(0, () => {
    // react to change
})

unsubscribeFromCount()
            

How it works?

The state is a synchronous value which means you can update it in one line and read it on the next.

javascript
                setCount(10)
console.log(count()) // logs 10
            

Behind the scenes, state is just a self managed subscription that works seamlessly with effect and html APIs.

Examples

Form input value and validation:

javascript
                const [valid, setValid] = state(true)
const [value, setValue] = state('', () => {
    if (value().length > 8) {
        setValid(false)
    }

    setValid(true)
})
            
edit this doc