JSFiddle - React, Tailwind, and code Playground
by David Kyle
HTML
<span class="flex horizontal"><label for="is-on-state">Is On?</label><input id="is-on-state" name="is-on-state" type="checkbox" /></span>
<span id="output-state"></span>
<button id="start-button" value="start">
Start
</button>
<button id="stop-button" value="stop">
Stop
</button>
CSS
#output-state {
border: solid 1px #000000;
border-radius: .2rem;
padding: .2rem;
display: block;
width: 100px;
height: 20px;
margin: .2rem 0;
}
.flex {
display: flex;
flex-direction: row;
}
.horizontal {
vertical-align: middle;
align-content: center;
}
JavaScript
/**
* A set of configuration properties for the observer.
* @typedef {Object} ObserverConfiguration
* @property {(any) => void | null} next The function to invoke when the observable value changes.
* @property {(any) => void | null} error The function to invoke when an error occurrs in the observable.
* @property {() => void | null} complete The function to invoke when the observable completes.
*/
/**
* Rudimentary implementation of observables to simplify state management.
*/
export class Observer {
#handlers
#isUnsubscribed
/**
* Initializes a new instance of the Observable class.
* @param {ObserverConfiguration} handlers The handles to configure for the observer.
*/
constructor(handlers) {
this.#handlers = handlers
this.#isUnsubscribed = false
}
/**
* Fired when the observable value changes.
* @param {any} value The value being set on the observable.
*/
next(value) {
if (this.#handlers.next && !this.#isUnsubscribed) {
this.#handlers.next(value)
}
}
/**
* Fired when an error occurs on the observable.
* @param {any} error The error information.
*/
error(error) {
if (!this.#isUnsubscribed) {
if (this.#handlers.error) {
this.#handlers.error(error)
}
this.unsubscribe()
}
}
/**
* Fired when the observable is completed.
*/
complete() {
if (!this.#isUnsubscribed) {
if (this.#handlers.complete) {
this.#handlers.complete()
}
this.unsubscribe()
}
}
/**
* Unhooks the observer from the observable.
*/
unsubscribe() {
this.#isUnsubscribed = true
if (this._unsubscribe) {
this._unsubscribe()
}
}
}
export class Observable {
constructor(subscribe) {
this._subscribe = subscribe
}
subscribe(obs) {
const observer = new Observer(obs)
observer._unsubscribe = this._subscribe(observer)
return {
unsubscribe() {
observer.unsubscribe()
},
}
...