react-bucketjs
v2.0.1
Published
React state-management using elegant buckets with hooks to support re-render on changes.
Downloads
314
Maintainers
Readme
React BucketJS
Ultra-lightweight state management for React. Create a bucket to hold any value, update it from anywhere, and components that use it re-render automatically.
- Works with primitives, arrays, and objects
- Fine-grained re-render control via selectors and comparators
- No dependencies — only React 18+ as a peer dependency
npm install react-bucketjsCore concept
A bucket is a tiny store you create outside of any component. Export it from a shared file and import it wherever you need it — no providers, no context.
// store.js
import { bucket } from "react-bucketjs";
// TypeScript infers the type from the initial value
export const btCount = bucket(0); // BucketType<number>
// Or provide the generic explicitly
export const btCount = bucket<number>(0);
// Useful when the type is wider than the initial value
type Status = "idle" | "loading" | "error";
export const btStatus = bucket<Status>("idle");
// Objects work the same way
type User = { name: string; age: number };
export const btUser = bucket<User>({ name: "Alice", age: 30 });// Counter.jsx
import { useBucket } from "react-bucketjs";
import { btCount } from "./store";
function Counter() {
const count = useBucket(btCount);
return (
<div>
<h1>{count}</h1>
<button onClick={() => btCount.set(count + 1)}>Increment</button>
</div>
);
}Hooks
useBucket<T>(bucket, comparator?)
Subscribe to the entire bucket state. Re-renders the component whenever the state changes.
// Type is inferred from the bucket
const count = useBucket(btCount); // number | undefined
// Or provide it explicitly
const count = useBucket<number>(btCount); // number | undefineduseBucketSelector<T, R>(bucket, selector, comparator?)
Subscribe to a single derived value. The component only re-renders when that specific value changes — other fields in the bucket are ignored.
T is the bucket state type, R is the type returned by the selector.
type User = { name: string; age: number };
const btUser = bucket<User>({ name: "Alice", age: 30 });
function Name() {
// T and R are both inferred from the bucket and selector
const name = useBucketSelector(btUser, (s) => s.name); // string
// Or provide them explicitly
const name = useBucketSelector<User, string>(btUser, (s) => s.name);
return <span>{name}</span>;
}useBucketSelectorX<T, R>(bucket, selector, comparator?)
Same as useBucketSelector but returns a [value] tuple. Useful when you want to destructure a single value in a way that is easy to extend later.
// Inferred
const [name] = useBucketSelectorX(btUser, (s) => s.name); // [string]
// Explicit
const [name] = useBucketSelectorX<User, string>(btUser, (s) => s.name);useBuckets(buckets[], comparator?)
Subscribe to multiple buckets at once. Returns an array of their current state values, in the same order as the input array. Re-renders whenever any of them changes.
The return type is inferred as a plain any[], so explicitly typing your buckets is the recommended way to keep things type-safe.
const btA = bucket<number>(1);
const btB = bucket<string>("hello");
function Display() {
const [a, b] = useBuckets([btA, btB]); // [number, string]
return <p>{a} — {b}</p>;
}Bucket methods
Once you have a bucket you can read and write its state from anywhere — inside or outside of React components.
| Method | Description |
|---|---|
| bucket.get(selector?) | Read the current state. Pass an optional selector to read a derived value. |
| bucket.set(value \| updater) | Replace the state. Pass a function to derive the next value from the current one. |
| bucket.cset(value \| updater) | Same as set but deep-clones the value before storing it. |
| bucket.assign(partial) | Shallow-merge a partial object into the current state (like Object.assign). |
| bucket.cassign(partial) | Same as assign but deep-clones the partial before merging. |
| bucket.copy(selector?) | Returns a deep clone of the state (or a selected slice). |
| bucket.subscribe(fn) | Register a listener called on every change. Returns an unsubscribe function. |
| bucket.emit() | Manually notify all subscribers without changing the state. |
set with an updater function
const btCount = bucket(0);
btCount.set((current) => current + 1); // 1
btCount.set((current) => current + 1); // 2assign for partial object updates
const btUser = bucket({ name: "Alice", age: 30 });
btUser.assign({ age: 31 });
// state is now { name: "Alice", age: 31 }Comparators
By default, re-renders are triggered by reference equality (===). Pass a comparator to take control.
A comparator receives the previous and next value. Return true to skip the re-render, false to allow it.
import { useBucket, compareStringified } from "react-bucketjs";
// Re-render only when the `role` field changes
const byRole = (prev, next) => prev.role === next.role;
const user = useBucket(btUser, byRole);
// Re-render based on deep equality (JSON comparison)
const value = useBucket(btData, compareStringified);compareStringified is a built-in helper that returns true when two values serialize to the same JSON string (i.e. they are deeply equal), suppressing the re-render.
Subscribing outside React
Use bucket.subscribe to react to changes in non-component code such as logging, analytics, or synchronisation.
Note: the subscriber receives the internal store wrapper
{ state: T }, so access your value viastore.state.
import { bucket } from "react-bucketjs";
const btCounter = bucket(0);
const unsubscribe = btCounter.subscribe((store) => {
console.log("Count:", store.state);
});
btCounter.set(1); // Count: 1
btCounter.set(2); // Count: 2
unsubscribe(); // stop listening