Observable
A proxy based state manager & reactive programming library
- Easy to use and great DX. See examples below;
- Supports classes and plain objects;
- Supports subclassing;
- Tiny (2.7 kB), no dependencies;
- Framework-agnostic.
It comes with a hoc for React, as the most popular library, but can be used with any other.
Getting Started with react
import { Observable, observer } from "kr-observable";
class State extends Observable {
results: string[] = []
text = ''
loading = false
setText(event: Event) {
this.text = event.target.value
}
async search() {
try {
this.loading = true
const response = await fetch('/someApi')
this.results = await response.json()
} catch(e) {
console.warn(e)
} finally {
this.loading = false
}
}
reset() {
this.results = []
}
}
const state = new State()
const Results = observer(function results() {
return (
<div>
{state.results.map(result => <div key={result}>{result}</div>)}
</div>
)
})
const Component = observer(function component() {
return (
<div>
<input
placeholder="Text..."
onChange={state.setText}
disabled={state.loading}
value={state.text}
/>
<button
onClick={state.search}
disabled={state.loading}
>
Submit
</button>
<button onClick={state.reset}>
Reset
</button>
<Results />
</div>
)
})
More complicated example on CodeSandbox
Api reference
observer
The observer converts a React component into a reactive component, which tracks observables and re-renders the component when one of these changes.
Can only be used for function components.
interface Options {
name?: string
debug?: boolean
}
type observer<P> = (baseComponent: FunctionComponent<P>, options?: Options) => FunctionComponent<P>
Observable class
import { Observable } from 'kr-observable'
class Foo extends Observable {
}
- All properties are observable by default. Arrays and plain objects are deep observable.
- All getters are computed by default
- All methods are
bounded by default
- Private properties (#prop) are just private properties, you can use them
- All subclasses are also observable
listen, unlisten, subscribe and unsubscribe are reserved. They won't work even on accidentally redefine.
Their signature below.
type Subscriber = (property: string | symbol, value: any) => void
type Listener = () => void
interface Observable {
listen(cb: Listener): void
unlisten(cb: Listener): void
subscribe(cb: Subscriber, keys: Set<keyof Observable>): void
unsubscribe(cb: Subscriber): void
}
Example
import { Observable } from "kr-observable";
class Example extends Observable {
#private = 1
string = ''
number = 0
array = []
set = new Set()
map = new Map()
plain = {
foo: 'baz',
nestedArray: []
}
get something() {
return this.number + this.string
}
}
const example = new Example()
const listener = (property: string | symbol, value: any) => {
console.log(`${property} was changed, new value = `, value)
}
const subscriber = () => {
console.log('subscriber was notified')
}
example.listen(listener)
example.subscribe(subscriber, new Set(['string', 'number', 'array']))
example.string = 'hello'
example.number = 2
example.array.push('string')
example.array = []
example.plain.foo = ''
example.plain.nestedArray.push(42)
Ignore properties
The static ignore property allows you to exclude some properties
import { Observable } from 'kr-observable';
class Foo extends Observable {
static ignore = ['foo']
foo = 1
bar = 2
}
makeObservable
Has the same API as Observable, but works only with plain objects
import { makeObservable } from 'kr-observable';
const observableObject = makeObservable({
foo: 'bar',
count: 0,
increaseCount() {
this.count++
}
})
autorun
The autorun function accepts one function that should run every time anything it observes changes.
It also runs once when you create the autorun itself.
import { Observable, autorun } from 'kr-observable';
class Example extends Observable {
one = 0
two = 0
}
const example = new Example()
autorun(() => console.log('total', example.one + example.two))
setInterval(() => {
example.one += 1
}, 1000)
Performance
Is fast enough.

Memory usage

Size
~2.7 kb. See BundlePhobia
Limitations
There is only one limitation: if you assign a new element to the array by index – changes will happen, of course, but You will not be notified.
import { Observable } from 'kr-observable';
class Example extends Observable {
array = []
}
const state = new Example()
state.listen((p,v) => console.log(p,v))
state.array[0] = 1
state.set(0,1)
There is a new set method in Array which you can use for that.