Tired of boilerplate-heavy state management? Observable is a lightweight, intuitive library that brings seamless reactivity to JavaScript with minimal effort.
Why Observable?
Observable is inspired by MobX
but designed to be even simpler. It gives you complete freedom to update state anywhere — even inside effects or reaction callbacks. No special wrappers, annotations, or strict rules are needed. Just modify your data naturally, and Observable automatically tracks changes and updates exactly what needs to change.
- Simple: Write plain JavaScript — no complex APIs;
- Reactive: Automatically syncs state and UI;
- Tiny: 3KB gzipped, zero dependencies;
- Async-friendly: Handle asynchronous operations without extra syntax.
Example: Dynamic Post Viewer
Let’s build a post viewer that:
- Fetches posts on demand
- Handles loading/error states
- Updates the UI automatically
Define State (Plain JS Class):
const API = 'https://jsonplaceholder.typicode.com/posts'
class State {
loading = true;
postId = 1;
post = null;
error = null;
async getPost() {
try {
this.loading = true;
const response = await fetch(`${API}/${this.postId}`);
this.post = await response.json();
this.error = null;
} catch (error) {
this.post = null;
this.error = error.message;
} finally {
this.loading = false;
}
}
}
const state = new State();
Create React Component:
function Posts() {
return (
<div>
{state.loading ? "Loading..." : null}
{state.post ? (
<h2>{state.post.title}</h2>
) : (
<p>Error: {state.error || "No post found."}</p>
)}
<button onClick={() => state.postId--}>Prev</button>
<button onClick={() => state.postId++}>Next</button>
</div>
);
}
Add Reactivity (3 Lines!):
import { Observable, autorun } from "kr-observable";
import { observer } from "kr-observable/react";
// 1. Implement reactive state by extending Observable
class State extends Observable { /* ... */ }
// 2. Convert Posts to observable component
export const ObservedPosts = observer(Posts);
// 3. Auto-fetch posts when postId changes
autorun(state.getPost);
That’s it! Now:
- Clicking Prev/Next auto-fetches new posts;
- Loading and error states update instantly;
- No manual subscriptions.
Try it on stackblitz.com
Final code
import { Observable, autorun } from 'kr-observable'
import { observer } from 'kr-observable/react'
class State extends Observable {
loading = true;
postId = 1;
post = null;
error = null;
async getPost() {
try {
this.loading = true;
const response = await fetch(`/posts/${this.postId}`);
this.post = await response.json();
this.error = null;
} catch (error) {
this.post = null;
this.error = error.message;
} finally {
this.loading = false;
}
}
prev() {
this.postId -= 1;
}
next() {
this.postId += 1;
}
}
const state = new State();
const dispose = autorun(state.getPost);
function Posts() {
return (
<div>
<div>Loading: {String(state.loading)}</div>
{state.post ? (
<h2>{state.post.title}</h2>
) : (
<p>Error: {state.error || "No post found."}</p>
)}
<div>
<button onClick={state.prev}>
Prev
</button>
<button onClick={state.next}>
Next
</button>
</div>
</div>
);
}
export const ObservedPosts = observer(Posts)
What else?
Observable can do much more. It includes built-in adapters for React, Preact and Vue. Uses 38% less memory than Redux in benchmarks, and is really friendly.
Learn More
Discussion
-
MobX
users: How does this compare to your experience? - What’s missing? What would make
Observable
better for your projects? - How does this fit with your current state management setup?
(Disclosure: I’m a maintainer. Open to feedback and contributions!)
Top comments (0)