From 55cb4eb6a61c4c1c37782944b10bfef87b045e98 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Sun, 15 Feb 2026 12:24:02 +0000
Subject: [PATCH 1/2] Rewrite documentation for clarity and impact
Co-authored-by: quynhtrang0309 <247292619+quynhtrang0309@users.noreply.github.com>
---
AI_CONTEXT.md | 117 ++++----
API_DOCUMENTATION.md | 628 +++++++------------------------------------
README.md | 552 ++++++-------------------------------
3 files changed, 239 insertions(+), 1058 deletions(-)
diff --git a/AI_CONTEXT.md b/AI_CONTEXT.md
index cbeb468..39f7aa6 100644
--- a/AI_CONTEXT.md
+++ b/AI_CONTEXT.md
@@ -1,44 +1,45 @@
-# React State Custom - AI Context & Best Practices
+# React State Custom - AI Context
-This document is designed to help AI assistants (Gemini, ChatGPT, Claude, Copilot) understand the preferred patterns and API usage for `react-state-custom`.
+**Target Audience:** AI Assistants (Gemini, ChatGPT, Claude, Copilot)
+**Goal:** Generate idiomatic, high-performance code using `react-state-custom`.
-## ๐ง Mental Model
+---
+
+## ๐ง Mental Model: "It's Just a Hook"
+
+`react-state-custom` turns standard React hooks into shared global stores.
-`react-state-custom` is a **hook-first** state management library.
-- **State is just a hook**: You define state logic using standard React hooks (`useState`, `useEffect`, `useMemo`).
-- **Stores are headless components**: The library runs your hook in a hidden component (`Root`) and publishes the results to an event-driven context.
-- **Consumption is selective**: Components subscribe only to the specific fields they need, preventing unnecessary re-renders.
+1. **Define Logic**: Write a hook (`useState`, `useEffect`, `useMemo`).
+2. **Share Logic**: Wrap it with `createStore`.
+3. **Consume Logic**: Use the generated hook in any component.
+
+**Key Difference:** Unlike Redux or Zustand, there is no external store object. The "store" is literally a React component running your hook in the background, managed by `AutoRootCtx`.
+
+---
-## ๐ The "Golden Path" (Preferred API)
+## ๐ The Golden Path
-Always prefer `createStore` over the lower-level `createRootCtx` / `createAutoCtx` primitives unless specifically requested.
+**Always** follow this pattern unless explicitly asked for low-level primitives.
-### 1. Define State (The Hook)
-Write a standard React hook. It receives `params` (props) and `preState` (warm-start data).
+### 1. The Store (Features/State)
```typescript
-// state.ts
+// features/counterState.ts
import { useState } from 'react';
+import { createStore } from 'react-state-custom';
-export const useCounterState = ({ initial = 0 }: { initial?: number }) => {
+// 1. Define the hook (Standard React)
+const useCounterState = ({ initial = 0 }: { initial?: number }) => {
const [count, setCount] = useState(initial);
const increment = () => setCount(c => c + 1);
return { count, increment };
};
-```
-
-### 2. Create Store (The Factory)
-Use `createStore` to generate the store hooks.
-
-```typescript
-// state.ts
-import { createStore } from 'react-state-custom';
+// 2. Export the store (One line)
export const { useStore: useCounterStore } = createStore('counter', useCounterState);
```
-### 3. Mount Context (The Root)
-Mount `` **once** near the top of the app.
+### 2. The Root (App Entry)
```tsx
// App.tsx
@@ -47,22 +48,22 @@ import { AutoRootCtx } from 'react-state-custom';
export default function App() {
return (
<>
-
+ {/* ๐ Must be at the top */}
>
);
}
```
-### 4. Consume State (The Component)
-Use the generated `useStore` hook. It returns a proxy that tracks usage.
+### 3. The Consumer (Components)
```tsx
-// Counter.tsx
-import { useCounterStore } from './state';
+// components/Counter.tsx
+import { useCounterStore } from '../features/counterState';
export function Counter() {
- // โก๏ธ Only re-renders when 'count' changes
+ // 3. Use the hook
+ // โก๏ธ Automatic subscription: re-renders ONLY when 'count' changes.
const { count, increment } = useCounterStore({ initial: 10 });
return ;
@@ -71,37 +72,41 @@ export function Counter() {
---
-## โ Do's and โ Don'ts
+## โ Best Practices
-### โ DO
-- **DO** use `createStore` for 95% of use cases.
-- **DO** use `AutoRootCtx` to manage store lifecycles automatically.
-- **DO** keep store parameters simple (primitives like strings/numbers) to ensure stable context IDs.
-- **DO** destructure `useStore` results immediately during render (e.g., `const { data } = useStore(...)`).
-- **DO** use `preState` in your hook if you need to persist state across hot-reloads or auto-remounts.
+- **Params must be primitives**: Store parameters (`{ id: '123' }`) are serialized to create unique store instances. Avoid passing objects or callbacks as params.
+- **Destructure immediately**: `const { data } = useStore(...)`. The returned object is a proxy that tracks usage during render.
+- **No Providers**: Never manually wrap components in providers. `AutoRootCtx` handles everything.
+- **Keep it simple**: Don't use `createRootCtx` or `createAutoCtx` directly. `createStore` is the only API you usually need.
-### โ DON'T
-- **DON'T** use `createRootCtx` and `createAutoCtx` manually unless you are building a custom abstraction.
-- **DON'T** pass objects or arrays as store parameters (e.g., `useStore({ user: { id: 1 } })`). Pass IDs instead (`useStore({ userId: 1 })`).
-- **DON'T** store the proxy returned by `useStore` in a `useRef` or `useEffect`. It is designed for render-phase tracking only.
-- **DON'T** manually create `Context` objects via `new Context()` unless you are building a low-level primitive.
+## ๐ ๏ธ Common Patterns
-## ๐ API Signatures (Simplified)
+### Async Data (Data Fetching)
```typescript
-// The main factory function
-function createStore(
- name: string,
- useFn: (params: Params, preState: Partial) => State,
- timeToClean?: number // Optional: keep-alive time in ms
-): {
- useStore: (params: Params) => State; // Returns a reactive proxy
- useCtxState: (params: Params) => Context; // Returns the raw context
-}
+const useUserState = ({ userId }) => {
+ const [data, setData] = useState(null);
+
+ useEffect(() => {
+ fetchUser(userId).then(setData);
+ }, [userId]);
+
+ return { data, isLoading: !data };
+};
+```
+
+### Derived State
-// The global manager
-function AutoRootCtx(props: {
- Wrapper?: React.ComponentType; // e.g. ErrorBoundary
- debugging?: boolean;
-}): JSX.Element;
+Since stores are just hooks, you can use `useMemo` for derived data.
+
+```typescript
+const useCartState = () => {
+ const [items, setItems] = useState([]);
+
+ const total = useMemo(() =>
+ items.reduce((sum, item) => sum + item.price, 0)
+ , [items]);
+
+ return { items, total };
+};
```
diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md
index 5283be7..8f150e4 100644
--- a/API_DOCUMENTATION.md
+++ b/API_DOCUMENTATION.md
@@ -1,390 +1,67 @@
-# React State Custom - API Reference
+# API Reference
-a hook-first state management toolkit for React 19 applications. Every export surfaces typed utilities for wiring headless state containers, publishing updates, and consuming them with fine-grained subscriptions.
+Complete documentation for the `react-state-custom` API.
-> First time here? Skim the โQuick Start (2 minutes)โ section in `README.md` before diving into the API surface.
-
-๐ฎ **[Try the Live Demo โ](https://vothanhdat.github.io/react-state-custom/)**
-
----
-
-## Contents
-
-- [React State Custom - API Reference](#react-state-custom---api-reference)
- - [Contents](#contents)
- - [Core Context System](#core-context-system)
- - [`Context`](#context)
- - [`getContext`](#getcontext)
- - [`useDataContext`](#usedatacontext)
- - [Publishing Hooks](#publishing-hooks)
- - [`useDataSource`](#usedatasource)
- - [`useDataSourceMultiple`](#usedatasourcemultiple)
- - [Subscription Hooks](#subscription-hooks)
- - [`useDataSubscribe`](#usedatasubscribe)
- - [`useDataSubscribeMultiple`](#usedatasubscribemultiple)
- - [`useDataSubscribeMultipleWithDebounce`](#usedatasubscribemultiplewithdebounce)
- - [`useDataSubscribeWithTransform`](#usedatasubscribewithtransform)
- - [`useQuickSubscribe`](#usequicksubscribe)
- - [Store Factory](#store-factory)
- - [`createStore`](#createstore)
- - [Root Context Factory](#root-context-factory)
- - [`createRootCtx`](#createrootctx)
- - [Auto Context System](#auto-context-system)
- - [`AutoRootCtx`](#autorootctx)
- - [`createAutoCtx`](#createautoctx)
- - [Developer Tools](#developer-tools)
- - [`DevToolContainer`](#devtoolcontainer)
- - [`DataViewComponent`](#dataviewcomponent)
- - [Utility Hooks](#utility-hooks)
- - [`useArrayChangeId`](#usearraychangeid)
- - [Types](#types)
- - [`ParamsToIdRecord`](#paramstoidrecord)
- - [Usage Patterns](#usage-patterns)
- - [Basic Context Wiring](#basic-context-wiring)
- - [Headless Root + Auto Context](#headless-root--auto-context)
- - [Live Examples](#live-examples)
-
----
-
-## Core Context System
-
-### `Context`
-
-EventTarget-backed data store that tracks the latest values for a shape `D` and notifies listeners on change.
-
-```ts
-class Context extends EventTarget {
- constructor(name: string)
- name: string
- data: Partial
- registry: Set
- useCounter: number
- publish(key: keyof D, value: D[keyof D] | undefined): void
- subscribe(key: keyof D, listener: (value: D[keyof D] | undefined) => void): () => void
- subscribeAll(listener: (key: keyof D, snapshot: Partial) => void): () => void
-}
-```
-
-- Publishes only when `value != data[key]` (loose inequality for shallow detection).
-- `subscribe` immediately invokes the listener with the current value if present.
-- `subscribeAll` fires for every key update via an internal `@--change-event` broadcast.
-- `registry` collects active publishers for duplicate detection (see `useDataSource*`).
-
-```ts
-interface UserState { name: string; age: number }
-const users = new Context("users")
-const stop = users.subscribe("name", value => console.log("name โ", value))
-users.publish("name", "Ada Lovelace")
-stop()
-```
-
-### `getContext`
-
-Memoized `Context` factory keyed by name.
-
-```ts
-function getContext(name: string): Context
-```
-
-- Returns the same instance for repeated calls with identical arguments.
-- Internally memoized via JSON-stringified arguments and pruned when `useCounter` stays at zero for ~100 ms after unmount.
-
-```ts
-const sessionCtx = getContext("session")
-const sameInstance = getContext("session")
-console.assert(sessionCtx === sameInstance)
-```
-
-### `useDataContext`
-
-React hook returning a typed `Context` by name.
-
-```ts
-function useDataContext(name?: string): Context
-```
-
-- Memoizes `getContext(name)` and increments `ctx.useCounter` while mounted.
-- Automatically evicts unused contexts shortly after the last consumer unmounts.
-
-```tsx
-function SessionProvider({ children }: { children: React.ReactNode }) {
- const ctx = useDataContext<{ token?: string }>("session")
- useDataSource(ctx, "token", readSessionToken())
- return <>{children}>
-}
-```
-
----
-
-## Publishing Hooks
-
-### `useDataSource`
-
-Publishes a single key whenever `value` changes.
-
-```ts
-function useDataSource(
- ctx: Context | undefined,
- key: K,
- value: D[K] | undefined
-): void
-```
-
-- Publishes inside an effect to avoid React render side effects.
-- Skips publication when `ctx` is undefined or `value == ctx.data[key]`.
-- Registers `key` in `ctx.registry`; duplicate publishers log an error with a captured stack trace.
-
-```tsx
-function UserSource({ userId }: { userId: string }) {
- const ctx = useDataContext("user")
- const profile = useUserProfile(userId)
- useDataSource(ctx, "profile", profile)
- return null
-}
-```
-
-### `useDataSourceMultiple`
-
-Batch publisher for multiple `[key, value]` tuples.
-
-```ts
-function useDataSourceMultiple(
- ctx: Context | undefined,
- ...entries: { -readonly [P in keyof T]: [T[P], D[T[P]]] }
-): void
-```
-
-- Shallow-compares each pair and publishes only the changed ones.
-- Dependencies collapse to a hash from [`useArrayChangeId`](#usearraychangeid); pass stable tuples to avoid extra flushes.
-- Runs the same duplicate-source safety check as `useDataSource`.
-
-```tsx
-useDataSourceMultiple(ctx,
- ["user", user],
- ["theme", theme],
- ["isLoading", loading],
-)
-```
+> **Note:** For most applications, you only need `createStore` and `AutoRootCtx`. The other APIs are lower-level primitives used internally or for advanced customization.
---
-## Subscription Hooks
-
-### `useDataSubscribe`
-
-Subscribes to a single key with optional debounce.
-
-```ts
-function useDataSubscribe(
- ctx: Context | undefined,
- key: K,
- debounceTime?: number
-): D[K] | undefined
-```
-
-- Uses React state to trigger re-renders; initial state mirrors `ctx?.data[key]`.
-- `debounceTime > 0` wraps updates in `debounce` with `.cancel()` support on cleanup.
-- Returns the live value (`ctx?.data[key]`) so reads stay current even if the hook is mid-debounce.
-
-```tsx
-const searchQuery = useDataSubscribe(ctx, "query", 250)
-```
-
-### `useDataSubscribeMultiple`
-
-Observes several keys and re-renders when any value differs from the previous snapshot.
-
-```ts
-function useDataSubscribeMultiple(
- ctx: Context | undefined,
- ...keys: K
-): { [P in K[number]]: D[P] | undefined }
-```
-
-- Aggregates values into an object keyed by the provided names.
-- Internally debounces change detection to the next macrotask (1 ms) to coalesce bursts.
-- Automatically unsubscribes all listeners on unmount.
-
-```tsx
-const { user, isLoading } = useDataSubscribeMultiple(ctx, "user", "isLoading")
-```
-
-### `useDataSubscribeMultipleWithDebounce`
-
-Like `useDataSubscribeMultiple`, but allows specifying a debounce window.
-
-```ts
-function useDataSubscribeMultipleWithDebounce(
- ctx: Context | undefined,
- debounceTime?: number,
- ...keys: K
-): { [i in keyof K]: D[K[i]] | undefined }
-```
-
-- Default debounce is 50 ms.
-- Returns an array keyed by index (matches the order of `keys`).
-
-```tsx
-const [query, filters] = useDataSubscribeMultipleWithDebounce(ctx, 200, "query", "filters")
-```
-
-### `useDataSubscribeWithTransform`
-
-Subscribes to one key and recomputes derived data lazily.
-
-```ts
-function useDataSubscribeWithTransform(
- ctx: Context | undefined,
- key: K,
- transform: (value: D[K] | undefined) => E
-): E
-```
-
-- Memoizes `transform(ctx?.data[key])` and only triggers the internal `setState` when the transformed value actually changes (`!=`).
-- Handy for formatting or computing derived values without extra selectors.
-
-```tsx
-const stats = useDataSubscribeWithTransform(ctx, "profile", profile => ({
- postCount: profile?.posts?.length ?? 0,
- joinedAt: profile?.createdAt ? new Date(profile.createdAt) : null,
-}))
-```
-
-### `useQuickSubscribe`
-
-Proxy-based subscription that tracks which properties you read during render and subscribes to those keys only.
-
-```ts
-function useQuickSubscribe(
- ctx: Context | undefined
-): { [P in keyof D]?: D[P] | undefined }
-```
-
-- Wraps `ctx?.data` in a `Proxy`; the first read cycle records accessed keys.
-- After render, subscribes to the accessed set and cleans up unused subscriptions automatically.
-- Subsequent renders reuse the same proxy; avoid storing it outside render scope.
-
-```tsx
-const { total, items } = useQuickSubscribe(cartCtx)
-```
-
-> โ ๏ธ The proxy intentionally throws when accessed outside of render. Always destructure the fields you need synchronously during render and avoid passing the proxy to refs, effects, or callbacks.
-
----
-
-## Store Factory
+## โก Primary API
### `createStore`
-The all-in-one helper that combines `createRootCtx` and `createAutoCtx` into a single call. This is the recommended way to create stores in most applications.
+The main entry point. Converts a standard React hook into a shared, auto-managed store.
-```ts
-function createStore>(
+```typescript
+function createStore(
name: string,
- useFn: (params: U, preState: Partial) => V,
+ useFn: (params: Params, preState: Partial) => State,
timeToClean?: number,
- AttatchedComponent?: React.FC
+ AttachedComponent?: React.FC
): {
- useCtxState(params: U): Context
- useStore(params: U): { [P in keyof V]?: V[P] | undefined }
+ useStore(params: Params): State;
+ useCtxState(params: Params): Context;
}
```
-- **`name`**: Unique namespace for this store.
-- **`useFn`**: The hook that defines your state logic.
-- **`timeToClean`**: Optional delay (ms) before unmounting the store after the last subscriber leaves.
-- **`AttatchedComponent`**: Optional component to render alongside the store (useful for side effects).
+#### Arguments
+- **`name`** *(string)*: A unique namespace for this store (e.g., `'user'`, `'cart'`).
+- **`useFn`** *(function)*: Your custom hook. Receives `params` and optional `preState`.
+- **`timeToClean`** *(number, optional)*: Time in milliseconds to keep the store alive after the last subscriber unmounts. Defaults to `0`.
+- **`AttachedComponent`** *(Component, optional)*: A React component that renders alongside the store root. Useful for side effects (like data fetching or logging) that should run exactly once per store instance.
-Returns an object with:
-- **`useStore`**: A hook that subscribes to the store and returns a proxy for reading state.
-- **`useCtxState`**: A hook that returns the raw `Context` object (for advanced usage).
+#### Returns
+- **`useStore`**: The consumer hook. Call this in your components to read state. It returns a **proxy** that automatically tracks which properties you access to optimize re-renders.
+- **`useCtxState`**: Returns the raw `Context` object. Useful for advanced integrations.
+#### Example
```tsx
-// 1. Define store
-const { useStore } = createStore('counter', () => {
- const [count, setCount] = useState(0);
+const useCounter = ({ initial }) => {
+ const [count, setCount] = useState(initial);
return { count, setCount };
-});
+};
-// 2. Use in component
-function Counter() {
- const { count, setCount } = useStore({});
- return ;
-}
+export const { useStore } = createStore('counter', useCounter);
```
---
-## Root Context Factory
-
-### `createRootCtx`
-
-Creates a headless Root component that runs your hook `useFn(props, preState)` exactly once per parameter set and publishes its return shape into a derived context namespace. `preState` is the previously published data for that context name (if any), useful for rehydrating state after an auto-unmount/remount cycle.
-
-```ts
-import { ParamsToIdRecord } from 'react-state-custom'
-
-function createRootCtx>(
- name: string,
- useFn: (params: U, preState: Partial) => V
-): {
- name: string
- getCtxName(params: U): string
- useRootState(params: U): V
- Root: React.FC
- useCtxState(params: U): Context
- useCtxStateStrict(params: U): Context
-}
-```
-
-Key behaviors:
-
-- Context name = `name` plus serialized, sorted props (for example `user-state-userId-123`).
-- `useFn` receives the last published state (if any) as `preState`, so you can warm start when a Root remounts.
-- The generated `Root` publishes every key returned by `useFn` via `useDataSourceMultiple`.
-- Duplicate `Root` mounts with the same resolved name throw (stack trace captured at mount).
-- Props passed to `Root`/`useCtxState` must be primitives (string/number/boolean/bigint/null/undefined); `paramsToId` rejects objects so context IDs remain deterministic.
-- `useCtxStateStrict` throws if the `Root` is missing; `useCtxState` schedules a delayed `console.error` instead (1 s).
-
-```tsx
-const { Root: UserRoot, useCtxState } = createRootCtx("user", useUserState)
-
-function UserProvider({ userId, children }: { userId: string; children: React.ReactNode }) {
- return (
- <>
-
- {children}
- >
- )
-}
-
-function UserName({ userId }: { userId: string }) {
- const ctx = useCtxState({ userId })
- const { profile } = useDataSubscribeMultiple(ctx, "profile")
- return {profile?.name}
-}
-```
-
----
-
-## Auto Context System
-
### `AutoRootCtx`
-Global manager that renders requested Root instances lazily.
+The global manager component. Must be mounted once at the top of your application.
-```ts
+```typescript
function AutoRootCtx(props: {
- Wrapper?: React.FC
- debugging?: boolean
+ Wrapper?: React.FC<{ children: React.ReactNode }>;
+ debugging?: boolean;
}): JSX.Element
```
-- Mount exactly once near your app root.
-- Stores a registry of active roots in the special context `auto-ctx`, publishing `subscribe` and `state` for internal coordination.
-- Each subscription entry tracks usage count and optional delayed teardown (`keepUntil`).
-- `Wrapper` lets you provide an ErrorBoundary-like component; defaults to `Fragment`. Set `debugging` to render ephemeral state snapshots for inspection.
+#### Props
+- **`Wrapper`** *(Component, optional)*: A wrapper component for each store instance (e.g., an ErrorBoundary). Defaults to `React.Fragment`.
+- **`debugging`** *(boolean, optional)*: If `true`, renders a raw view of the state in the DOM for debugging.
+#### Example
```tsx
function App() {
return (
@@ -392,229 +69,110 @@ function App() {
>
- )
-}
-```
-
-### `createAutoCtx`
-
-Connects a `createRootCtx` factory to `AutoRootCtx`, returning a consumer hook that ensures the corresponding Root is mounted on demand.
-
-```ts
-function createAutoCtx>(
- rootCtx: ReturnType>,
- unmountDelayMs?: number,
- AttatchedComponent?: React.FC
-): {
- useCtxState(params: U): Context
- useStore(params: U): { [P in keyof V]?: V[P] | undefined }
+ );
}
```
-- Subscribes to the global `auto-ctx` context and asks `AutoRootCtx` to mount the root.
-- `unmountDelayMs` (default 0) keeps instances alive briefly after the last subscriber disconnects, smoothing mount/unmount thrash.
-- `AttatchedComponent` (optional) is a React component that receives the same params as the state hook. It renders alongside each auto-mounted root instance, useful for side effects, portals, or UI that should live alongside the state.
-- Consumers simply call `useCtxState(params)` or `useStore(params)`; no manual Root mounting required.
-
-```tsx
-const { useCtxState: useUserCtx, useStore: useUserStore } = createAutoCtx(createRootCtx("user", useUserState), 200)
-
-function UserCard({ userId }: { userId: string }) {
- // Quick access via useStore
- const { profile } = useUserStore({ userId })
-
- // Or manual context access
- const ctx = useUserCtx({ userId })
- // ...
- return {profile?.name}
-}
-```
-
-**With AttatchedComponent:**
-
-```tsx
-// A component that logs when a user context is active
-const UserLogger: React.FC<{ userId: string }> = ({ userId }) => {
- useEffect(() => {
- console.log(`User ${userId} context mounted`)
- return () => console.log(`User ${userId} context unmounted`)
- }, [userId])
- return null
-}
-
-const { useCtxState: useUserCtx } = createAutoCtx(
- createRootCtx("user", useUserState),
- 200,
- UserLogger
-)
-```
-
---
-## Developer Tools
+## ๐ ๏ธ Developer Tools
### `DevToolContainer`
-Popup inspector that renders the current data snapshot for every context.
+A floating inspector to visualize all active stores and their state in real-time.
-```ts
+```typescript
function DevToolContainer(props: {
- toggleButton?: string
- Component?: DataViewComponent
- style?: React.CSSProperties
- children?: React.ReactNode
+ toggleButton?: string;
+ Component?: DataViewComponent;
+ style?: React.CSSProperties;
+ children?: React.ReactNode;
}): JSX.Element
```
-- Togglable overlay; pass `Component` to customize how values render (defaults to JSON).
-- Docks to the bottom of the viewport and exposes resizable panes (via `@uiw/react-split`) so you can balance app viewport and inspector real estate on the fly.
-- Import `react-state-custom/dist/react-state-custom.css` to get the required styles.
-- Provide `children` to override the floating toggle button label (defaults to "Toggle Dev Tool").
-- Works best alongside `` so all contexts are represented.
+#### Props
+- **`toggleButton`** *(string, optional)*: Text for the toggle button.
+- **`Component`** *(Component, optional)*: Custom renderer for state values. Defaults to a JSON tree view.
+- **`children`** *(ReactNode, optional)*: Custom trigger button content.
+#### Example
```tsx
-import "react-state-custom/dist/react-state-custom.css"
+import { DevToolContainer } from 'react-state-custom';
+import 'react-state-custom/dist/react-state-custom.css';
-function DevShell() {
- return (
- <>
-
-
-
- >
- )
-}
-```
-
-### `DataViewComponent`
-
-Type for custom dev tool renderers.
-
-```ts
-type DataViewComponent = React.FC<{ name: string; value: any }>
-```
-
-```tsx
-const ObjectViewer: DataViewComponent = ({ name, value }) => (
-
-
{name}
-
{JSON.stringify(value, null, 2)}
-
-)
+
```
---
-## Utility Hooks
+## โ๏ธ Advanced API (Primitives)
-### `useArrayChangeId`
+These APIs are used internally by `createStore`. You generally don't need them unless you're building custom abstractions.
-Shallow change detector for arrays. Returns a random string that flips whenever the tracked array differs from the last render.
-
-```ts
-function useArrayChangeId(values: any[]): string
-```
+### `createRootCtx`
-- Compares length and each element via `!=`.
-- Useful for collapsing complex tuple dependencies into a single stable key.
+Creates a headless "Root" component that runs a hook and publishes its result to a context.
-```tsx
-const changeKey = useArrayChangeId(entries.flat())
-useEffect(() => {
- // expensive work runs only when the tuple content changes
-}, [changeKey])
+```typescript
+function createRootCtx(
+ name: string,
+ useFn: (params: Params, preState: Partial) => State
+): {
+ Root: React.FC;
+ useCtxState(params: Params): Context;
+ // ...other internal helpers
+}
```
----
-
-## Types
-
-### `ParamsToIdRecord`
+### `createAutoCtx`
-Type constraint for parameters passed to `createRootCtx` and `createAutoCtx`. Only primitive values are allowed to ensure deterministic context naming.
+Connects a `createRootCtx` result to the `AutoRootCtx` system for automatic mounting.
-```ts
-type ParamsToIdRecord = Record
+```typescript
+function createAutoCtx(
+ rootCtx: ReturnType,
+ timeToClean?: number,
+ AttachedComponent?: React.FC
+): Result
```
-- Keys must be strings
-- Values must be primitives: `string`, `number`, `bigint`, `boolean`, `null`, or `undefined`
-- Objects, arrays, and functions are **not allowed** and will throw at runtime via `paramsToId`
+### `useDataContext`
-```tsx
-// โ Valid params
-const validParams = { userId: "123", count: 42, active: true }
+Hook to retrieve a `Context` instance by name.
-// โ Invalid params (will throw)
-const invalidParams = { user: { id: 123 } } // Objects not allowed
-const invalidParams2 = { callback: () => {} } // Functions not allowed
+```typescript
+function useDataContext(name: string): Context
```
-This constraint ensures that context names remain deterministic and stable across renders, preventing accidental context duplication or collision.
-
----
+### `useDataSource` / `useDataSourceMultiple`
-## Usage Patterns
+Hooks to publish values from a component into a context.
-### Basic Context Wiring
+```typescript
+useDataSource(ctx, 'key', value);
+useDataSourceMultiple(ctx, ['key1', value1], ['key2', value2]);
+```
-```tsx
-interface AppState {
- user?: User
- theme: "light" | "dark"
-}
+### `useDataSubscribe` / `useDataSubscribeMultiple`
-function AppRoot() {
- const ctx = useDataContext("app-state")
- useDataSourceMultiple(ctx,
- ["user", useCurrentUser()],
- ["theme", useTheme()],
- )
- return
-}
+Hooks to manually subscribe to specific context keys.
-function NavBar() {
- const ctx = useDataContext("app-state")
- const { user, theme } = useDataSubscribeMultiple(ctx, "user", "theme")
- return Welcome {user?.name}
-}
+```typescript
+const value = useDataSubscribe(ctx, 'key');
+const { key1, key2 } = useDataSubscribeMultiple(ctx, 'key1', 'key2');
```
-### Headless Root + Auto Context
+---
-```tsx
-function useCartState({ cartId }: { cartId: string }) {
- const [items, setItems] = useState([])
- const total = useMemo(() => items.reduce((sum, item) => sum + item.price * item.qty, 0), [items])
- return { items, total, setItems }
-}
+## ๐งฉ Types
-const cartRoot = createRootCtx("cart", useCartState)
-const { useCtxState: useCartCtx } = createAutoCtx(cartRoot, 250)
+### `ParamsToIdRecord`
-function CartPanel({ cartId }: { cartId: string }) {
- const ctx = useCartCtx({ cartId })
- const { items, total } = useQuickSubscribe(ctx)
- return (
-
- )
-}
+Constraint for store parameters. All params must be primitive values to ensure deterministic IDs.
-function Shell() {
- return (
- <>
-
-
- >
- )
-}
+```typescript
+type ParamsToIdRecord = Record<
+ string,
+ string | number | bigint | boolean | null | undefined
+>;
```
-
----
-
-## Live Examples
-
-Explore full working demos (counter, todo list, form, timer, cart) at the **[Live Demo](https://vothanhdat.github.io/react-state-custom/)**.
diff --git a/README.md b/README.md
index 2f5facb..0a7e0f0 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
# React State Custom
-**Simple. Powerful. TypeScript-first.**
+**The "It's Just a Hook" State Manager for React.**
-Turn any React hook into global state. No boilerplate, no complexityโjust pure, performant state management.
+Turn any React hook into a global store. Zero boilerplate. Full type safety. Automatic lifecycle management.
[](https://vothanhdat.github.io/react-state-custom/)
[](https://www.npmjs.com/package/react-state-custom)
@@ -14,522 +14,145 @@ npm install react-state-custom
๐ฎ **[Try the Live Demo โ](https://vothanhdat.github.io/react-state-custom/)**
-## Quick Start (2 minutes)
+---
-If you already know how to write a component with `useState`, you're moments away from sharing that state everywhere.
+## โก The 30-Second Pitch
-1. **Write a plain hook** โ encapuslate data fetching, derived values, and actions inside a normal React hook.
-2. **Create a store** โ `createStore('feature', useFeatureState)` creates a shared store and returns a `useStore` hook.
-3. **Mount `` once** โ drop it near the top of your tree (wrap it with your own `ErrorBoundary` if desired).
-4. **Consume anywhere** โ call the generated `useStore` hook to access data and actions.
+Stop writing reducers, actions, and manual providers. If you can write a React hook, you've already written your store.
```tsx
-import { useState, useMemo } from 'react'
-import { createStore, AutoRootCtx } from 'react-state-custom'
-
-const useFeatureState = ({ featureId }: { featureId: string }) => {
- const [value, setValue] = useState(0)
- const double = useMemo(() => value * 2, [value])
- return { value, double, increment: () => setValue(v => v + 1) }
-}
-
-export const { useStore: useFeatureStore } = createStore('feature', useFeatureState)
-
-function AppShell() {
- return (
- <>
-
-
- >
- )
-}
-
-function FeatureMeter({ featureId }: { featureId: string }) {
- const { value, double, increment } = useFeatureStore({ featureId })
- return (
-
- {value}
- {double}
-
-
- )
+// 1. Write a standard hook (your store logic)
+const useCountState = ({ initial = 0 }) => {
+ const [count, setCount] = useState(initial)
+ const increment = () => setCount(c => c + 1)
+ return { count, increment }
}
-```
-
-Thatโs the entire workflowโno reducers, actions, or provider trees.
-
-## Why React State Custom?
-
-**Zero Boilerplate** โข **Type-Safe** โข **Selective Re-renders** โข **Hook-Based** โข **~10KB Bundle**
-
-React State Custom lets you write state management code that feels naturalโbecause it **is** just React hooks. Use the same hooks you already know (`useState`, `useEffect`, etc.) to create powerful, shared global state without learning new paradigms.
-
-### When `useState` + `useEffect` Fall Short
-
-Even though React hooks are flexible, they start to hurt once state crosses component boundaries:
-- **Prop drilling & manual providers** โ every time state needs to be shared, you create a context, memoize values, and remember to wrap trees.
-- **Coarse-grained re-renders** โ updating one property forces every subscriber of that context to render, even if they don't consume the changed field.
-- **Lifecycle bookkeeping** โ you manually manage instance lifetimes, clean up effects, and guard against components mounting before providers.
-- **Zero visibility** โ there's no built-in way to inspect shared state, throttle noisy updates, or keep debugging breadcrumbs.
-
-React State Custom keeps your favorite hooks but layers on automatic context lifecycles, selective subscriptions, and built-in tooling so you can stay productive as your app grows.
-
-## โก Quick Example
-
-### Without React State Custom (manual context plumbing)
-
-```typescript
-const CounterContext = createContext<{
- count: number;
- increment: () => void;
- decrement: () => void;
-} | null>(null);
-
-function CounterProvider({ children }: { children: React.ReactNode }) {
- const [count, setCount] = useState(0);
- const value = useMemo(
- () => ({
- count,
- increment: () => setCount(c => c + 1),
- decrement: () => setCount(c => c - 1),
- }),
- [count]
- );
-
- return {children};
-}
-
-function useCounter() {
- const ctx = useContext(CounterContext);
- if (!ctx) throw new Error('CounterProvider missing');
- return ctx;
-}
-```
-
-Every consumer re-renders whenever anything in `value` changes, you have to remember to wrap parts of the tree with `CounterProvider`, and tearing this pattern down for parameterized instances gets messy fast.
-
-### With React State Custom (hook-first store)
-
-### With React State Custom (hook-first store)
-
-```typescript
-import { useState } from 'react';
-import { createStore, AutoRootCtx } from 'react-state-custom';
-
-// 1. Write your state logic using familiar React hooks
-function useCounterState() {
- const [count, setCount] = useState(0);
- const increment = () => setCount(c => c + 1);
- const decrement = () => setCount(c => c - 1);
-
- return { count, increment, decrement };
-}
-
-// 2. Create shared store (one line!)
-const { useStore } = createStore('counter', useCounterState);
-
-// 3. Add AutoRootCtx to your app root (mount it once near the top of your tree)
-function App() {
- return (
- <>
-
-
- >
- );
-}
+// 2. Create a store
+export const { useStore } = createStore('counter', useCountState)
-// 4. Use anywhere in your app
+// 3. Use it anywhere
function Counter() {
- const { count, increment, decrement } = useStore({});
-
- return (
-
-
{count}
-
-
-
- );
+ const { count, increment } = useStore({ initial: 10 })
+ return
}
```
-> โน๏ธ `AutoRootCtx` accepts optional `Wrapper` and `debugging` props. Pass an ErrorBoundary-like component through `Wrapper` to isolate failures, or set `debugging` to `true` to render raw state snapshots in the DOM (handy alongside React DevTools when tracking updates).
-
-`useStore` keeps `Counter` focused on `count`, so even if this context grows with more fields later, the component only re-renders when `count` changes.
+**That's it.** No `Provider` wrapping. No complex setup. Just hooks.
-**That's it!** No reducers, no actions, no providers to wrapโjust hooks.
+---
-## Core Concepts in Plain English
+## ๐ Why React State Custom?
-- **Contexts on demand** โ `Context` extends `EventTarget`, so every state update is just an event dispatch. `getContext` memoizes instances per name and `useDataContext` automatically bumps a counter so unused contexts self-evict shortly after the last consumer unmounts.
-- **Publishers** โ `useDataSource` and `useDataSourceMultiple` publish inside effects to keep renders pure. A registry guards against duplicate publishers fighting over the same key so you get actionable errors instead of stale data.
-- **Subscribers** โ `useDataSubscribe*` hooks cover single, multiple, debounced, and transformed reads. `useQuickSubscribe` proxies the backing data object so each component subscribes only to the properties it touches.
-- **Root factories** โ `createRootCtx` runs your headless hook exactly once per parameter set, publishes every returned key, and throws if two roots try to mount with the same resolved name. Your hook receives `(props, preState)` so it can rehydrate from the last published values when a root remounts. Parameters are serialized via `paramsToId`, so stick to primitive props (string/number/boolean/bigint/null/undefined) to keep IDs deterministic.
-- **Composable Stores** โ Because stores are just hooks, you can subscribe to one store *inside* the logic of another. This enables powerful reactive chains where a derived store automatically updates whenever its upstream dependencies change.
-- **Auto orchestration** โ Mount `` once and wire each root through `createAutoCtx`. The auto root listens for subscription requests, mounts/destroys the corresponding root on demand, and optionally keeps them alive for a configurable `timeToClean` window to smooth thrashing.
-- **Dev tooling** โ `DevToolContainer` watches the memoized context cache, flashes updates in place, and lets you plug in custom renderers so you can diff state right beside your UI.
+Most state libraries force you to learn a new way to write logic (reducers, atoms, proxies). **React State Custom** lets you use the React skills you already have.
-## Core Building Blocks (copy & paste ready)
+### ๐ Zero Boilerplate
+Define state with `useState`, `useEffect`, `useMemo`. No new syntax to learn.
-Familiarity beats theory, so here are the primitives youโll reach for most often:
+### ๐ฏ Selective Re-renders
+Components only re-render when the specific data they use changes. Performance is built-in.
-### 1. Context โ event-driven store
-```typescript
-const ctx = useDataContext('my-state');
-```
+### ๐ Automatic Lifecycle
+Stores are created when needed and destroyed when unused. No more manual cleanup or memory leaks.
-### 2. Data source โ publish values
-```typescript
-useDataSource(ctx, 'count', count);
-```
+### ๐ก๏ธ TypeScript First
+Full type inference out of the box. Your IDE knows exactly what's in your store.
-### 3. Subscribers โ pick exact fields
-```typescript
-const count = useDataSubscribe(ctx, 'count');
-const { count, name } = useDataSubscribeMultiple(ctx, 'count', 'name');
-```
+---
-### 4. Root context โ run your hook once
-```typescript
-const { Root, useCtxState } = createRootCtx('my-state', useMyState);
-```
+## ๐ ๏ธ Quick Start
-### 5. Auto context โ mount roots for you
-```typescript
-const { useCtxState } = createAutoCtx(rootContext);
-```
+### 1. Define Your State
+Write a hook that returns the data and actions you want to share.
-### 6. Store factory โ all in one
```typescript
-const { useStore } = createStore('my-state', useMyState);
-```
+// features/userState.ts
+import { useState, useEffect } from 'react'
-### 6. Store factory โ all in one
-```typescript
-const { useStore } = createStore('my-state', useMyState);
-```
-
-## ๐ฏ Key Features
-
-### 1. **Just React Hooks**
-Use `useState`, `useEffect`, `useMemo`, and any other React hooks you already know. No new concepts to learn.
-
-```typescript
-function useUserState({ userId }: { userId: string }) {
- const [user, setUser] = useState(null);
- const [loading, setLoading] = useState(true);
+export const useUserState = ({ userId }: { userId: string }) => {
+ const [user, setUser] = useState(null)
useEffect(() => {
- fetchUser(userId).then(setUser).finally(() => setLoading(false));
- }, [userId]);
-
- return { user, loading };
+ fetchUser(userId).then(setUser)
+ }, [userId])
+
+ return { user, isLoading: !user }
}
```
-### 2. **Selective Re-renders**
-Components only re-render when the **specific data they subscribe to** changesโnot when anything in the state changes.
+### 2. Create the Store
+Use `createStore` to generate a hook for your components.
```typescript
-// Only re-renders when 'user' changes, not when 'loading' changes
-const { user } = useDataSubscribeMultiple(ctx, 'user');
+import { createStore } from 'react-state-custom'
+import { useUserState } from './features/userState'
-// Or subscribe to multiple fields
-const { user, loading } = useDataSubscribeMultiple(ctx, 'user', 'loading');
+export const { useStore: useUserStore } = createStore('user', useUserState)
```
-> โ ๏ธ `useQuickSubscribe` proxies are only readable during render. Destructure the properties you need immediately and avoid storing the proxy in refs, effects, or callbacks.
-
-### 3. **Automatic Context Management**
-With `AutoRootCtx`, state contexts are automatically created and destroyed as needed. Mount it once near your application root, optionally providing a `Wrapper` (for error boundaries) or enabling `debugging` to render live state snapshots in the DOMโuseful context when pairing with React DevTools. No manual provider management required.
-
-### 4. **TypeScript First**
-Full type inference and type safety throughout. Your IDE knows exactly what's in your state.
-
-### 5. **Tiny Bundle Size**
-~10KB gzipped. No dependencies except React.
-
-## ๐ Comparison with Hooks, Redux & Zustand
-
-| Feature | React State Custom | Plain Hooks (Context) | Redux | Zustand |
-|---------|-------------------|-----------------------|-------|---------|
-| **Bundle Size** | ~10KB | 0KB (just React) | ~50KB (with toolkit) | ~1KB |
-| **Learning Curve** | โ Minimal (just hooks) | โ ๏ธ Familiar APIs, but patterns are DIY | โ High (actions, reducers, middleware) | โ Low |
-| **Boilerplate** | โ None | โ Manual providers + prop drilling | โ Heavy | โ Minimal |
-| **Type Safety** | โ Full inference | โ ๏ธ Custom per-context typing | โ ๏ธ Requires setup | โ Good |
-| **Selective Re-renders** | โ Built-in | โ Context update = every consumer renders | โ ๏ธ Requires selectors | โ Built-in |
-| **DevTools** | โ Built-in UI | โ None | โ Redux DevTools | โ DevTools support |
-| **Async Support** | โ Native (hooks) | โ Native (hooks) | โ ๏ธ Requires middleware | โ Native |
-| **Context Composition** | โ Automatic | โ Manual provider trees | โ Manual | โ ๏ธ Manual store combination |
-
-### When to Use React State Custom
-
-โ **Choose React State Custom if you:**
-- Want to use React hooks for state management without learning new patterns
-- Need fine-grained control over component re-renders
-- Prefer minimal boilerplate and configuration
-- Want automatic context lifecycle management
-- Need multiple independent state contexts that don't interfere
-
-โ **Consider Redux if you:**
-- Need powerful time-travel debugging (Redux DevTools)
-- Have a very large team that benefits from strict architectural patterns
-- Already have significant Redux investment
-
-โ **Consider Zustand if you:**
-- Want the absolute smallest bundle size
-- Need a simple global store without context isolation
-- Don't need automatic context lifecycle management
-
-## ๐ฅ Real-World Example: User Authentication
-
-```typescript
-// authState.ts
-function useAuthState() {
- const [user, setUser] = useState(null);
- const [loading, setLoading] = useState(true);
-
- useEffect(() => {
- // Check authentication on mount
- checkAuth().then(setUser).finally(() => setLoading(false));
- }, []);
-
- const login = async (email: string, password: string) => {
- setLoading(true);
- try {
- const user = await authService.login(email, password);
- setUser(user);
- } finally {
- setLoading(false);
- }
- };
-
- const logout = async () => {
- await authService.logout();
- setUser(null);
- };
-
- return { user, loading, login, logout };
-}
-
-export const { useStore: useAuthStore } = createStore('auth', useAuthState);
+### 3. Mount the Root (Once)
+Add `` to your app's root. This component manages all your stores automatically.
+```tsx
// App.tsx
-function App() {
- return (
- <>
-
-
-
-
-
- >
- );
-}
+import { AutoRootCtx } from 'react-state-custom'
-// Header.tsx - Only re-renders when user changes
-function Header() {
- const { user, logout } = useAuthStore({});
-
- return (
-
- {user ? (
- <>
- Welcome, {user.name}
-
- >
- ) : (
- Login
- )}
-
- );
-}
-
-// ProtectedRoute.tsx - Only re-renders when loading or user changes
-function ProtectedRoute({ children }) {
- const ctx = useAuthStore.useCtxState({});
- const { user, loading } = useDataSubscribeMultiple(ctx, 'user', 'loading');
-
- if (loading) return ;
- if (!user) return ;
-
- return children;
-}
-```
-
-**Compare with Redux:**
-```typescript
-// Redux requires: action types, action creators, reducers, thunks/sagas
-// React State Custom: just write a hook! โจ
-```
-
-## ๐ Advanced Features
-
-Once you have a store running, layer in these power-ups as needed.
-
-### Developer Tools
-Visual debugging component to inspect all your context data in real-time:
-
-```typescript
-import { DevToolContainer } from 'react-state-custom';
-import 'react-state-custom/dist/react-state-custom.css';
-
-function App() {
+export default function App() {
return (
<>
-
>
- );
+ )
}
```
-The toggle reveals a bottom-docked inspector that now uses resizable split panes powered by `@uiw/react-split`. Drag the gutter to adjust how much space the context list or detail view occupies while keeping your application visible above.
-
-**Custom data viewer with rich object visualization:**
-```typescript
-import { DataViewComponent } from 'react-state-custom';
-import { ObjectView } from 'react-obj-view';
-import 'react-obj-view/dist/react-obj-view.css'; // Required for ObjectView styling
-
-const CustomDataView: DataViewComponent = ({ name, value }) => {
- return ;
-};
-
-
-```
-
-Pass `children` to `DevToolContainer` to customize the floating toggle button label (for example `State Inspector`), and import `react-state-custom/dist/react-state-custom.css` once to pick up the overlay styles.
-
-### Parameterized Contexts
-Create multiple instances of the same state with different parameters:
-
-```typescript
-function useUserState({ userId }: { userId: string }) {
- // State logic here
-}
-
-const { useStore: useUserStore } = createStore('user', useUserState);
+---
-// Different instances for different users
-function UserProfile({ userId }) {
- const { user } = useUserStore({ userId }); // Automatic instance per userId
- return
{user?.name}
;
-}
-```
+## ๐ Comparison
-> Need to avoid rapid mount/unmount churn? Pass a second argument to `createStore` (for example `createStore('user', useUserState, 200)`) to keep instances alive for a few extra milliseconds before disposal.
+| Feature | React State Custom | Redux | Context API | Zustand |
+|:---|:---:|:---:|:---:|:---:|
+| **Paradigm** | Just Hooks ๐ช | Actions/Reducers | Providers | Store Object |
+| **Boilerplate** | ๐ข None | ๐ด High | ๐ก Medium | ๐ข Low |
+| **Auto Lifecycle** | โ Yes | โ No | โ No | โ No |
+| **Selective Renders** | โ Automatic | โ ๏ธ Selectors | โ Manual | โ Selectors |
+| **Learning Curve** | ๐ข Low | ๐ด High | ๐ก Medium | ๐ข Low |
-> โ ๏ธ The props you pass to `createStore`/`useStore` must be composed of primitive values (string, number, boolean, bigint, null, or undefined). Objects are rejected so context names stay deterministicโpass IDs instead of raw objects.
+---
-### Debounced Subscriptions
-Optimize performance for frequently changing values:
+## ๐งฉ Advanced Features
-```typescript
-// Re-render at most once per 300ms
-const searchQuery = useDataSubscribe(ctx, 'searchQuery', 300);
-```
+### ๐ Developer Tools
+Inspect your state in real-time with the built-in DevTools.
-### Transformed Subscriptions
-Transform data before using it:
+```tsx
+import { DevToolContainer } from 'react-state-custom'
+import 'react-state-custom/dist/react-state-custom.css'
-```typescript
-const userStats = useDataSubscribeWithTransform(
- ctx,
- 'user',
- (user) => ({
- fullName: `${user?.firstName} ${user?.lastName}`,
- isAdmin: user?.role === 'admin'
- })
-);
+
```
-### Composing Stores (Derived State)
-Since stores are just hooks, you can subscribe to one store *inside* another. This allows you to build reactive dependency chains where a downstream store automatically updates when an upstream store changes.
+### ๐ Parameterized Stores
+Create multiple independent instances of the same store by passing different parameters.
-```typescript
-// 1. Upstream Store
-const { useStore: useUserStore } = createStore('user', () => {
- const [role, setRole] = useState('guest');
- return { role, setRole };
-});
-
-// 2. Downstream Store (depends on User)
-const useDashboardStore = () => {
- // Subscribe to the upstream store
- const { role } = useUserStore({});
-
- // Derive state based on the upstream value
- const permissions = useMemo(() => {
- return role === 'admin' ? ['read', 'write', 'delete'] : ['read'];
- }, [role]);
-
- return { permissions };
-};
-
-const { useStore: useDashboardStore } = createStore('dashboard', useDashboardStore);
+```tsx
+// Creates a unique store for each ID
+const { count } = useStore({ id: 'counter-1' })
+const { count } = useStore({ id: 'counter-2' })
```
-## ๐ฎ Live Examples
-
-Explore interactive examples in the **[Live Demo](https://vothanhdat.github.io/react-state-custom/)**:
-
-- **Counter** - Basic state management with increment, decrement, and reset
-- **Todo List** - Multiple independent lists with scoped contexts
-- **Form Validation** - Real-time validation with error handling
-- **Timer** - Side effects and cleanup with millisecond precision
-- **Shopping Cart** - Complex state with derived values (total, itemCount)
-
-Each example includes live code editing with syntax highlighting, powered by Sandpack!
-
-## ๐ Documentation
-
-For complete API documentation, examples, and advanced patterns, see:
-- **[API_DOCUMENTATION.md](./API_DOCUMENTATION.md)** - Complete API reference
-- **[AI_CONTEXT.md](./AI_CONTEXT.md)** - Context for AI assistants (Gemini, ChatGPT, etc.)
-- **[Live Demo](https://vothanhdat.github.io/react-state-custom/)** - Interactive examples
+### โก๏ธ Derived State
+Compose stores just like hooks.
-## ๐ ๏ธ Development
-
-```bash
-# Install dependencies
-yarn install
-
-# Run development UI with example selector
-yarn dev
-
-# Run interactive playground with live code editing
-yarn dev:playground
-
-# Build library
-yarn build
-
-# Build demo site
-yarn build:demo
-
-# Preview demo locally
-yarn preview
+```tsx
+const useCartTotal = () => {
+ const { items } = useCartStore({})
+ return items.reduce((total, item) => total + item.price, 0)
+}
```
-### Development Modes
-
-**`yarn dev`** - Starts a clean development UI with an interactive example selector. Great for:
-- Testing all examples in one place
-- Quick switching between different examples
-- Visual debugging with DevTool component
-
-**`yarn dev:playground`** - Starts the Sandpack-powered playground with live code editing. Perfect for:
-- Creating interactive demos
-- Live code editing and experimentation
-- Sharing editable examples
-
-## ๐ Learning Path
-
-1. **Follow the Quick Start** โ build one shared store end-to-end.
-2. **Layer on subscriptions** โ swap `useQuickSubscribe` for the more specific `useDataSubscribe*` hooks where it makes sense.
-3. **Optimize when needed** โ introduce debounced/transform subscriptions and `createAutoCtx` grace periods to smooth noisy stores.
-4. **Scale up** โ add parameterized contexts (one store per ID) and wire the DevTool overlay for visibility.
+---
## ๐ฆ Installation
@@ -537,18 +160,13 @@ yarn preview
npm install react-state-custom
# or
yarn add react-state-custom
-# or
-pnpm add react-state-custom
```
-## ๐ค Contributing
+## ๐ Documentation
-Contributions are welcome! Please feel free to submit a Pull Request.
+- **[API Reference](./API_DOCUMENTATION.md)** - Full API documentation.
+- **[Live Demo](https://vothanhdat.github.io/react-state-custom/)** - Interactive examples.
## ๐ License
-MIT License - feel free to use in any project.
-
----
-
-**Made with โค๏ธ for developers who love React hooks**
+MIT ยฉ Vo Thanh Dat
From 90852ec836332532d6a6a0322549248eb8936553 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Sun, 15 Feb 2026 12:28:47 +0000
Subject: [PATCH 2/2] Rewrite documentation for clarity and impact (including
AutoRootCtx fix)
Co-authored-by: quynhtrang0309 <247292619+quynhtrang0309@users.noreply.github.com>
---
README.md | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 0a7e0f0..c2325eb 100644
--- a/README.md
+++ b/README.md
@@ -31,14 +31,23 @@ const useCountState = ({ initial = 0 }) => {
// 2. Create a store
export const { useStore } = createStore('counter', useCountState)
-// 3. Use it anywhere
+// 3. Setup (mount once at root) & Use anywhere
+function App() {
+ return (
+ <>
+ {/* ๐ The magic that manages your stores */}
+
+ >
+ )
+}
+
function Counter() {
const { count, increment } = useStore({ initial: 10 })
return
}
```
-**That's it.** No `Provider` wrapping. No complex setup. Just hooks.
+**That's it.** No `Provider` wrapping per store. No complex setup. Just hooks.
---