Skip to main content

Reactivity

WICG Observable (Web Standard)

Proton adopts observable as another primitive. Any object that has Symbol.subscribe considered to be an observable.

Beyond that, it treats any object that has either get or set to be an accessor. Which can be combined with observable, getting an Observable Accessor.

It might seem to be a problematic decision considering Map and Set built-ins, but this is a start to solving it and in practice it's almost never a problem.


There is no built-in state manager (in the library), Proton only handles Observables as is. Now creating data flows is your problem :) Joking, it always was, now it's separated from the "framework", so you can use any library that supports Observables. Like the one Denshya has - Reactive.

const regularText = "Static Value"
const observableText = {
i: 0,
get: () => "Initial",
[Symbol.subscribe](next) {
setInterval(() => next("Next: " + this.i++))
}
}

Custom State

To implement a state, all you need is this simple snippet.

class State<T> {
private readonly callbacks = new Set<(value: T) => void>()
private value: T

constructor(initialValue: T) { this.value = initialValue }

get(): T { return this.value }
set(value: T): void {
this.value = value
this.dispatch(value)
}

private dispatch(value: T) {
this.callbacks.forEach(callback => callback(value))
}

[Symbol.subscribe](next: (value: T) => void) {
this.callbacks.add(next)
return { unsubscribe: () => void this.callbacks.delete(next) }
}
}

In practice it looks like this

function ProductCard() {
const title = new State("Title")
const image = new State("Image source")

return (
<>
<div className="product-card__title">{title}</div>
<img className="product-card__image" src={image} alt="Preview" />

<input value={title} />
<input value={image} />
</>
)
}

To get started faster with this, try Reactive state library.

[!NOTE] Observables are now implemented in Browsers. They can be used as it without any updates.

<div style={{ left: window.when("scroll").map(event => event.y + "px") }} />

Example Playground

Dual binding

Some elements may have binding in two directions, which means that [state updates element] and [element updates state].

This is common for user input elements just like input and textarea.

If you're using Reactive library, you can avoid this behavior with readonly method.

function ProductCard() {
const title = new State("Title")

return (
<>
// User inputs won't update the `title`.
<input value={title.readonly()} />
// User inputs WILL update the `title`.
<input value={title} />
</>
)
}

Using @denshya/reactive

StateArray

import { State, StateArray } from "@denshya/reactive"
function App() {
const choice = new State(0)
const items = new StateArray(Array(3).fill(0))

setInterval(() => items.set(Array(Math.floor(Math.random() * 15)).fill(0)), 1000)

return (
<div>
{items.map((_, index) => (
<button disabled={choice.is(index)} on={{ click: () => choice.set(index) }}>{index}</button>
))}
</div>
)
}