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
1const [count, setCount] = state(0)2 3const btn = document.createElement('button')4btn.type = 'button'5 6btn.addEventListener('click', () => {7    setCount((prev) => prev + 1)8})9 10effect(() => {11    btn.textContent = `count: ${count()}`12})13 14document.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
1const handleClick = () => {2    setCount((prev) => prev + 1)3}4 5html`<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
1const [count] = state<number>(0, () => {2    // react to change3})

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
1const [count, updateCount, unsubscribe] = state(0, () => {2    // will only get called once3})4 5updateCount(10)6 7unsubscribe()

StateGetter

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

javascript
1const [count] = state(0)2 3console.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
1const [count, updateCount] = state(0)2 3// provide a new value4updateCount(10)5 6// use current value and perform a calculation7updateCount(count() + 5)8 9// use the callback to update the value10updateCount((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
1const [count, updateCount] = state(0, () => {2    // this will never get called3    // given the setInterval update bellow4    console.log(count())5})6 7setInterval(() => {8    updateCount(0)9}, 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
1const [count, updateCount, unsubscribeFromCount] = state(0, () => {2    // react to change3})4 5unsubscribeFromCount()

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
1setCount(10)2console.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
1const [valid, setValid] = state(true)2const [value, setValue] = state('', () => {3    if (value().length > 8) {4        setValid(false)5    }6 7    setValid(true)8})
edit this doc