Introduction
Stunk - A lightweight, framework-agnostic state management library built on atomic chunk principles.
Stunk is a lightweight, reactive state management library built on atomic principles. Instead of one giant global store, you break state into small, independent pieces called chunks — each one self-contained, reactive, and composable.
No reducers. No boilerplate. No magic. Just state.
import { chunk } from "stunk";
const count = chunk(0);
count.get(); // 0
count.set(1); // set directly
count.set((n) => n + 1); // or derive from previous
count.peek(); // read without tracking dependencies
count.reset(); // back to 0Stunk is framework-agnostic at its core. React, Vue, Svelte, Solid, or vanilla
JS — it works everywhere. Official React integration ships with the package
via stunk/react.
How it works
Every piece of state in Stunk is a chunk — a tiny reactive container that holds a single value. Chunks can be read, updated, subscribed to, derived from, and composed together.
import { chunk, computed } from "stunk";
const price = chunk(100);
const quantity = chunk(3);
// Derived from a single chunk — read-only
const discounted = price.derive((p) => p * 0.9);
// Computed from multiple chunks — auto-tracks dependencies via .get()
const total = computed(() => price.get() * quantity.get());
total.get(); // 300 — updates automatically when price or quantity changesThink of chunks as the atoms of your application state. Small, focused, and composable.
computed() in v3 automatically discovers its dependencies by tracking which
chunks call .get() during execution — no dependency arrays needed. Use
.peek() inside a computed function to read a value without tracking it.
Key Features
Atomic State
Break state into independent chunks. No global store, no coupling.
Fine-Grained Reactivity
Only the components that depend on a chunk re-render when it changes.
Derive & Computed
Derive state from one chunk or compose across multiple with auto dependency tracking.
Async & Query
Built-in loading, error, caching, deduplication, and pagination via stunk/query.
Middleware
Extend chunks with logging, persistence, validation and more.
Time Travel
Undo and redo state changes with built-in history management.
React in 30 seconds
If you're using React, the stunk/react integration gives you hooks that are reactive out of the box:
import { chunk } from "stunk";
import { useChunk } from "stunk/react";
const counter = chunk(0);
function Counter() {
const [count, setCount] = useChunk(counter);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount((n) => n + 1)}>Increment</button>
</div>
);
}The component re-renders only when counter changes — nothing else.
Async in 30 seconds
The stunk/query subpackage brings a full async state layer — loading states,
error handling, caching, request deduplication, and pagination — with no extra dependencies:
import { asyncChunk } from "stunk/query";
const userChunk = asyncChunk(async ({ id }: { id: number }) => fetchUser(id), {
key: "user", // deduplicates concurrent requests
keepPreviousData: true, // no UI flicker on param changes
onSuccess: (data) => console.log("Loaded", data),
});
userChunk.setParams({ id: 1 });
// { loading: true, data: null, error: null }
// { loading: false, data: { id: 1, name: "..." }, error: null }Installation
npm install stunkpnpm add stunkyarn add stunkbun add stunk