Reactivity
WICG Observable (Web Standard)
Tama 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
MapandSetbuilt-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), Tama 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") }} />
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>
)
}