Package Exports
- react-rooks
- react-rooks/zod
Readme
React Rooks
React Rooks is a lightweight state management library for React that brings the simplicity of
useState
to global and contextual state. Just like a chess rook moves in straight lines, your state updates are direct, predictable, and powerful.
๐ฎ Try the Interactive Demo โ | ๐ Documentation | ๐ Quick Start
๐ฏ Why Choose React Rooks?
If you love useState
but need global state, React Rooks is for you. It's designed to feel exactly like React's built-in hooks while providing powerful state management capabilities.
๐ก New to React Rooks? Check out our interactive demo to see all features in action with real-time state inspection and code tooltips!
โจ Key Benefits
- ๐ชถ Ultra Lightweight: No runtime dependencies, pure React
- ๐ฃ Hook-First Design: Feels exactly like
useState
but for global state - ๐ Type-Safe: Full TypeScript support with automatic type inference
- โก Zero Boilerplate: No actions, reducers, or selectors required
- ๐งฉ Flexible: Global stores, scoped stores, multiple stores
- ๐ฆ Tree-Shakable: Only bundle what you use
๐ How React Rooks Compares
Feature | React Rooks | Redux Toolkit | Zustand | Jotai | Valtio |
---|---|---|---|---|---|
Bundle Size | ~2KB | ~47KB | ~8KB | ~13KB | ~14KB |
useState-like API | โ Perfect | โ Complex | โ Good | โ Good | โ ๏ธ Different |
TypeScript Support | โ Excellent | โ Good | โ Good | โ Excellent | โ Good |
Zero Boilerplate | โ | โ | โ | โ | โ |
Multiple Stores | โ | โ | โ | โ | โ |
Scoped Providers | โ | โ | โ ๏ธ Manual | โ | โ ๏ธ Manual |
Schema Validation | ๐ Coming | โ | โ | โ | โ |
Reducers | โ Optional | โ Required | โ Optional | โ | โ |
DevTools | โ | โ | โ | โ | โ |
๐ Quick Start
Installation
# Using bun (recommended)
bun add react-rooks
# Using npm
npm install react-rooks
# Using yarn
yarn add react-rooks
๐ฎ Try the Interactive Demo
Experience React Rooks in action with our interactive demo:
# Clone and run the demo locally
git clone https://github.com/your-username/react-rooks.git
cd react-rooks/demo
bun install
bun dev
Demo Features:
- ๐ Classic Examples: Todo lists, counters, user profiles
- ๐ฏ Advanced Patterns: Scoped stores, reducer integration, conditional rendering
- ๐ Reducer Examples: Complex state management with
useRook
and reducers - ๐ Real-time State Inspection: See state changes as they happen
- ๐ก Code Tooltips: Hover over components for implementation details
Basic Usage (Global Store)
import { createRook } from "react-rooks";
// 1. Create your store (like useState but global)
export const [UserStore, useUser] = createRook({
name: "John Doe",
email: "john@example.com",
theme: "light" as "light" | "dark",
});
// 2. Use anywhere in your app
function UserProfile() {
const [name, setName] = useUser("name");
const [email, setEmail] = useUser("email");
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<input value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
);
}
// 3. Wrap your app
function App() {
return (
<UserStore>
<UserProfile />
</UserStore>
);
}
๐ฏ useState vs useRook
React Rooks is designed to feel exactly like useState
:
useState (React) | useRook (React Rooks) |
---|---|
const [count, setCount] = useState(0) |
const [count, setCount] = useRook("count") |
Component-scoped | Global/contextual scope |
Direct updates | Direct updates |
Type-safe | Type-safe |
Simple API | Simple API |
Migration is Easy
// Before: Component state
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}
// After: Global state (same API!)
const [CounterStore, useCounter] = createRook({ count: 0 });
function Counter() {
const [count, setCount] = useCounter("count");
return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}
๐ง Upcoming Features
React Rooks is actively developed with exciting features coming soon:
- ๐ก๏ธ Schema Validation: Built-in validation system for your stores
- ๐ DevTools Integration: Debug your state changes with ease
- ๐ Performance Monitoring: Track re-renders and optimize performance
- ๐ Time Travel: Undo/redo functionality for development
- ๐พ Persistence: Automatic state persistence to localStorage/sessionStorage
๐ฎ Interactive Demo
Experience the full power of React Rooks with our comprehensive demo application that showcases all features in an interactive environment.
๐ Running the Demo
git clone https://github.com/your-username/react-rooks.git
cd react-rooks/demo
bun install && bun dev
๐ฏ What's Inside
The demo includes 5 comprehensive examples that demonstrate:
- ๐ Classic State Management: Todo lists, counters, and user profiles
- ๐ฏ Advanced Patterns: Scoped stores, multiple store instances, conditional rendering
- ๐ Reducer Integration: Complex state logic with
useRook
and reducer patterns - ๐ช Store Architecture: Global vs scoped stores, provider composition
- ๐ก Real-time Features: Live state inspection, interactive code tooltips
๐ Interactive Features
- Real-time State Viewer: Watch state changes as they happen across all stores
- Code Tooltips: Hover over any component to see implementation details and explanations
- Live Examples: Modify state and see immediate updates across multiple components
- Pattern Comparison: Side-by-side comparison of different state management approaches
๐ก Perfect for Learning: Each example includes detailed explanations and shows best practices for different use cases.
๐บ๏ธ Roadmap
๐ฏ Version 2.x - Performance & Lifecycle
๐ Performance Optimizations
- โก Smart Re-renders: Limit re-renders in
useRook
when the accessed key hasn't changed// Only re-render when 'name' changes, not when another key changes const [name] = useUser("name");
๐ Lifecycle Management
โ Initialization Callbacks: Execute callbacks when store initialization is complete
const [UserStore] = createRook( { user: null }, { onInitComplete: (store) => { console.log("Store initialized:", store); // Perfect for analytics, logging, or side effects }, } );
๐๏ธ Lazy Mounting: Allow components to render even before store initialization
Currently, Rook providers don't render their children until the store is fully initialized. This feature will allow components to mount immediately with loading states.
// Current behavior: Children don't render until store is ready function App() { return ( <UserStore> {/* Children wait for store initialization */} <UserProfile /> </UserStore> ); } // Future: Components can render with loading states before store is ready function UserProfile() { const { inited } = useRookState(UserStore); const [user] = useUserRook("user"); if (!inited) return <Spinner />; // Shows immediately return <div>{user.name}</div>; }
๐ก๏ธ Version 3.x - Schema Validation (Zod Integration)
๐ Zod v3 Support
- ๐ Runtime Validation: Automatic validation of store updates
- ๐ฏ Selective Validation: Validate only specific keys or entire store
- โ ๏ธ Error Boundaries: Graceful error handling for validation failures
import { createZodRook } from "react-rooks/zod";
import { z } from "zod";
const UserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(0).max(120),
});
const [UserStore, useUser] = createZodRook(UserSchema, {
validateOnUpdate: true,
onValidationError: (error) => {
// Handle validation errors gracefully
toast.error(`Invalid data: ${error.message}`);
},
});
๐ Version 4.x - Advanced Schema Features
๐ญ Enhanced Validation
- ๐ Schema Evolution: Handle schema migrations and versioning
- ๐ฏ Conditional Validation: Dynamic schemas based on store state
- ๐ Cross-Store Validation: Validate data across multiple stores
- ๐ Validation Analytics: Track validation errors and patterns
// Advanced schema with conditional validation
const UserSchema = z
.object({
type: z.enum(["admin", "user"]),
permissions: z.array(z.string()).optional(),
})
.refine(
(data) => {
// Conditional validation: admins must have permissions
if (data.type === "admin") {
return data.permissions && data.permissions.length > 0;
}
return true;
},
{
message: "Admin users must have at least one permission",
}
);
๐ API Reference
createRook(initialStore, reducers?)
Creates a global store with optional reducers.
import { createRook } from "react-rooks";
// Basic store
const [Store, useStore] = createRook({
count: 0,
user: { name: "", email: "" },
});
// With reducers
const [Store, useStore] = createRook(
{ count: 0 },
{
count: (state, action) => {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
default:
return state;
}
},
}
);
useRook(key?)
Hook to access store values.
// Access specific key
const [count, setCount] = useRook("count");
// Access entire store
const [store, setStore] = useRook();
// Functional updates
setCount((prevCount) => prevCount + 1);
// Direct updates
setCount(42);
RookContainer
Combine multiple stores.
import { RookContainer } from "react-rooks";
const [UserStore] = createRook({ name: "" });
const [ThemeStore] = createRook({ mode: "light" });
function App() {
return (
<RookContainer rooks={[UserStore, ThemeStore]}>
<MyApp />
</RookContainer>
);
}
โ ๏ธ Order Matters: Stores are nested from top to bottom. Components lower in the tree can access stores from higher levels.
// โ
Correct: CartStore can access UserStore and ThemeStore
<RookContainer rooks={[UserStore, ThemeStore, CartStore]}>
<App />
</RookContainer>
// โ Wrong: UserStore can't access CartStore or ThemeStore
<RookContainer rooks={[CartStore, ThemeStore, UserStore]}>
<App />
</RookContainer>
๐๏ธ Real-World Comparison: Shopping Cart
Let's see the same shopping cart feature implemented across different state management solutions. Same functionality, different complexity.
๐ React Rooks (22 lines)
import { createRook } from "react-rooks";
// 1. Create store (2 lines)
const [CartStore, useCart] = createRook({
items: [] as Array<{
id: number;
name: string;
price: number;
quantity: number;
}>,
total: 0,
});
// 2. Hook for actions (6 lines)
function useCartActions() {
const [items, setItems] = useCart("items");
const [, setTotal] = useCart("total");
const addItem = (item: (typeof items)[0]) => {
const newItems = [...items, item];
setItems(newItems);
setTotal(newItems.reduce((sum, i) => sum + i.price * i.quantity, 0));
};
const removeItem = (id: number) => {
const newItems = items.filter((i) => i.id !== id);
setItems(newItems);
setTotal(newItems.reduce((sum, i) => sum + i.price * i.quantity, 0));
};
return { addItem, removeItem };
}
// 3. Component (12 lines)
function ShoppingCart() {
const [items] = useCart("items");
const [total] = useCart("total");
const { addItem, removeItem } = useCartActions();
return (
<div>
<h2>
Cart ({items.length} items) - ${total.toFixed(2)}
</h2>
{items.map((item) => (
<div key={item.id}>
{item.name} - ${item.price} x {item.quantity}
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
<button
onClick={() =>
addItem({ id: Date.now(), name: "New Item", price: 10, quantity: 1 })
}
>
Add Item
</button>
</div>
);
}
// 4. App wrapper (2 lines)
function App() {
return (
<CartStore>
<ShoppingCart />
</CartStore>
);
}
๐ฆ Redux Toolkit (65 lines)
import { configureStore, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Provider, useSelector, useDispatch } from "react-redux";
// 1. Types (4 lines)
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
interface CartState {
items: CartItem[];
total: number;
}
// 2. Slice (22 lines)
const cartSlice = createSlice({
name: "cart",
initialState: {
items: [],
total: 0,
} as CartState,
reducers: {
addItem: (state, action: PayloadAction<CartItem>) => {
state.items.push(action.payload);
state.total = state.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
},
removeItem: (state, action: PayloadAction<number>) => {
state.items = state.items.filter((item) => item.id !== action.payload);
state.total = state.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
},
},
});
// 3. Store (3 lines)
const store = configureStore({
reducer: { cart: cartSlice.reducer },
});
type RootState = ReturnType<typeof store.getState>;
// 4. Actions (2 lines)
const { addItem, removeItem } = cartSlice.actions;
// 5. Component (20 lines)
function ShoppingCart() {
const { items, total } = useSelector((state: RootState) => state.cart);
const dispatch = useDispatch();
const handleAddItem = () => {
dispatch(
addItem({ id: Date.now(), name: "New Item", price: 10, quantity: 1 })
);
};
const handleRemoveItem = (id: number) => {
dispatch(removeItem(id));
};
return (
<div>
<h2>
Cart ({items.length} items) - ${total.toFixed(2)}
</h2>
{items.map((item) => (
<div key={item.id}>
{item.name} - ${item.price} x {item.quantity}
<button onClick={() => handleRemoveItem(item.id)}>Remove</button>
</div>
))}
<button onClick={handleAddItem}>Add Item</button>
</div>
);
}
// 6. App wrapper (6 lines)
function App() {
return (
<Provider store={store}>
<ShoppingCart />
</Provider>
);
}
๐ป Zustand (35 lines)
import { create } from "zustand";
// 1. Types (4 lines)
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
interface CartStore {
items: CartItem[];
total: number;
addItem: (item: CartItem) => void;
removeItem: (id: number) => void;
}
// 2. Store (18 lines)
const useCartStore = create<CartStore>((set, get) => ({
items: [],
total: 0,
addItem: (item) =>
set((state) => {
const newItems = [...state.items, item];
return {
items: newItems,
total: newItems.reduce((sum, i) => sum + i.price * i.quantity, 0),
};
}),
removeItem: (id) =>
set((state) => {
const newItems = state.items.filter((i) => i.id !== id);
return {
items: newItems,
total: newItems.reduce((sum, i) => sum + i.price * i.quantity, 0),
};
}),
}));
// 3. Component (12 lines)
function ShoppingCart() {
const { items, total, addItem, removeItem } = useCartStore();
return (
<div>
<h2>
Cart ({items.length} items) - ${total.toFixed(2)}
</h2>
{items.map((item) => (
<div key={item.id}>
{item.name} - ${item.price} x {item.quantity}
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
<button
onClick={() =>
addItem({ id: Date.now(), name: "New Item", price: 10, quantity: 1 })
}
>
Add Item
</button>
</div>
);
}
// 4. App (1 line)
function App() {
return <ShoppingCart />;
}
โ๏ธ Context + useState (45 lines)
import { createContext, useContext, useState, ReactNode } from "react";
// 1. Types (4 lines)
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
interface CartContextType {
items: CartItem[];
total: number;
addItem: (item: CartItem) => void;
removeItem: (id: number) => void;
}
// 2. Context (2 lines)
const CartContext = createContext<CartContextType | undefined>(undefined);
// 3. Provider (23 lines)
function CartProvider({ children }: { children: ReactNode }) {
const [items, setItems] = useState<CartItem[]>([]);
const [total, setTotal] = useState(0);
const addItem = (item: CartItem) => {
const newItems = [...items, item];
setItems(newItems);
setTotal(newItems.reduce((sum, i) => sum + i.price * i.quantity, 0));
};
const removeItem = (id: number) => {
const newItems = items.filter((i) => i.id !== id);
setItems(newItems);
setTotal(newItems.reduce((sum, i) => sum + i.price * i.quantity, 0));
};
return (
<CartContext.Provider value={{ items, total, addItem, removeItem }}>
{children}
</CartContext.Provider>
);
}
// 4. Hook (5 lines)
function useCart() {
const context = useContext(CartContext);
if (!context) {
throw new Error("useCart must be used within CartProvider");
}
return context;
}
// 5. Component (12 lines)
function ShoppingCart() {
const { items, total, addItem, removeItem } = useCart();
return (
<div>
<h2>
Cart ({items.length} items) - ${total.toFixed(2)}
</h2>
{items.map((item) => (
<div key={item.id}>
{item.name} - ${item.price} x {item.quantity}
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
<button
onClick={() =>
addItem({ id: Date.now(), name: "New Item", price: 10, quantity: 1 })
}
>
Add Item
</button>
</div>
);
}
// 6. App wrapper (3 lines)
function App() {
return (
<CartProvider>
<ShoppingCart />
</CartProvider>
);
}
๐ Comparison Results
Metric | React Rooks | Redux Toolkit | Zustand | Context + useState |
---|---|---|---|---|
Total Lines of Code | 22 โญ | 65 | 35 | 45 |
Concepts to Learn | 1 โญ | 5 | 2 | 3 |
Boilerplate Required | Minimal โญ | Heavy | Medium | Medium |
TypeScript Inference | Auto โญ | Manual | Manual | Manual |
useState-like API | โ โญ | โ | โ ๏ธ | โ |
Performance | Optimal โญ | Good | Good | Manual Optimization |
๐ฏ Why React Rooks Wins
- ๐ชถ Simplicity: Just 22 lines vs 65 for Redux Toolkit
- ๐ฃ Familiar API: Uses
useState
syntax you already know - ๐ Type Safety: Automatic TypeScript inference without manual typing
- โก Zero Concepts: No actions, reducers, selectors, or providers to learn
- ๐งฉ Direct Updates: Change state directly like
useState
Same functionality, 66% less code, 80% fewer concepts to learn. ๐
๐๏ธ Advanced Features
Multiple Stores with Scoping
// Create separate stores for different domains
const [UserStore, useUser] = createRook({
defaultStore: {
profile: { name: "", email: "" },
preferences: { theme: "light", notifications: true },
},
});
const [CartStore, useCart] = createRook({
defaultStore: {
items: [],
total: 0,
},
});
// Use them together
function App() {
return (
<RookContainer rooks={[UserStore, CartStore]}>
<Header />
<MainContent />
<Footer />
</RookContainer>
);
}
// Or scope them to specific parts of your app
function UserSection() {
return (
<UserStore>
<UserProfile />
<UserSettings />
</UserStore>
);
}
๐ Store Order and Dependencies
The order of stores in RookContainer
is crucial! Stores are nested from top to bottom, so components can only access stores that are positioned above them in the array.
// ๐ช Three stores with different responsibilities
const [UserStore, useUser] = createRook({
defaultStore: {
user: { id: 1, name: "Alice", role: "admin" },
isLoggedIn: true,
},
});
const [ThemeStore, useTheme] = createRook({
defaultStore: {
mode: "light" as "light" | "dark",
primaryColor: "#3b82f6",
},
});
const [CartStore, useCart] = createRook({
defaultStore: {
items: [] as CartItem[],
total: 0,
},
});
// Component that consumes from multiple stores
function SmartCartManager() {
// โ
Can access UserStore (positioned above)
const [user] = useUser("user");
const [isLoggedIn] = useUser("isLoggedIn");
// โ
Can access ThemeStore (positioned above)
const [mode] = useTheme("mode");
// โ
Can access CartStore (same level)
const [items, setItems] = useCart("items");
const addItem = () => {
if (!isLoggedIn) {
alert(`${user.name}, please log in first!`);
return;
}
const newItem = {
id: Date.now(),
name: `Item ${items.length + 1}`,
price: 29.99,
quantity: 1,
};
setItems([...items, newItem]);
};
return (
<div
style={{
backgroundColor: mode === "dark" ? "#1f2937" : "#f9fafb",
padding: "20px",
borderRadius: "8px",
}}
>
<h3>๐ Cart for {user.name}</h3>
<p>Theme: {mode} mode</p>
<button onClick={addItem} disabled={!isLoggedIn}>
Add Item to Cart
</button>
<p>Items in cart: {items.length}</p>
</div>
);
}
// โ
CORRECT: Proper order allows SmartCartManager to access all stores
function CorrectApp() {
return (
<RookContainer rooks={[UserStore, ThemeStore, CartStore]}>
<SmartCartManager />
</RookContainer>
);
}
// โ WRONG: CartStore is first, so SmartCartManager can't access UserStore/ThemeStore
function WrongApp() {
return (
<RookContainer rooks={[CartStore, UserStore, ThemeStore]}>
<SmartCartManager /> {/* This will throw errors! */}
</RookContainer>
);
}
๐ก RookContainer Best Practices
- Order by Dependency: Place stores that others depend on first
- Logical Hierarchy: User โ Theme โ Feature-specific stores
- Scope Appropriately: Only include stores that components actually need
// ๐ Recommended order patterns:
// For E-commerce app:
<RookContainer rooks={[
AuthStore, // 1. Authentication (most fundamental)
ThemeStore, // 2. UI preferences
UserStore, // 3. User data
CartStore, // 4. Shopping functionality
CheckoutStore // 5. Specific features
]}>
// For Admin dashboard:
<RookContainer rooks={[
AuthStore, // 1. Who is logged in?
PermissionStore,// 2. What can they do?
ThemeStore, // 3. How does it look?
DataStore // 4. Application data
]}>
// For Blog:
<RookContainer rooks={[
UserStore, // 1. Reader preferences
ThemeStore, // 2. Display settings
PostStore, // 3. Content data
CommentStore // 4. Interactive features
]}>
Custom Reducers
const [TodoStore, useTodos] = createRook(
{
todos: [],
filter: "all",
},
{
todos: (state, action) => {
switch (action.type) {
case "ADD_TODO":
return [...state, { id: Date.now(), text: action.text, done: false }];
case "TOGGLE_TODO":
return state.map((todo) =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
);
case "DELETE_TODO":
return state.filter((todo) => todo.id !== action.id);
default:
return state;
}
},
}
);
function TodoList() {
const [todos, dispatch] = useTodos("todos");
const addTodo = (text: string) => {
dispatch({ type: "ADD_TODO", text });
};
const toggleTodo = (id: number) => {
dispatch({ type: "TOGGLE_TODO", id });
};
}
Server-Side Rendering (SSR)
import { createRook } from "react-rooks";
// Initialize with server data
const [AppStore, useApp] = createRook({
user:
typeof window !== "undefined"
? JSON.parse(localStorage.getItem("user") || "null")
: null,
theme:
typeof window !== "undefined"
? localStorage.getItem("theme") || "light"
: "light",
});
// Hydrate on client
function App({ initialData }: { initialData?: any }) {
return (
<AppStore initialValue={initialData}>
<MyApp />
</AppStore>
);
}
๐ญ Philosophy: The Chess Rook
____
| |
|____|
| |
|____|
| |
/|____|\
/_|____|_\
| |
|____|
/ \
/____\
[ DATA ]
Just like a rook in chess, React Rooks represents:
- ๐ฐ Centralized Power: A strong, reliable foundation for your state
- ๐ Straight Lines: Direct, predictable state updates (no magic)
- โ๏ธ Strategic Position: Can be placed anywhere in your component tree
- ๐ Defensive: Type-safe and validated data protection
- โก Fast Movement: Instant updates across your entire application
The rook doesn't move in complex patternsโit's straightforward, powerful, and reliable. That's exactly how your state management should be.
๐ค Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Setup
git clone https://github.com/your-username/react-rooks.git
cd react-rooks
# Using bun (recommended)
bun install
bun run dev
# Using npm
npm install
npm run dev
# Using yarn
yarn install
yarn dev
๐ License
MIT ยฉ Your Name
๐ Acknowledgments
- Inspired by React's
useState
hook - Built with TypeScript for type safety
- Chess rook metaphor for clear mental model
Ready to move your state in straight lines? Start with React Rooks today! ๐ฐ