Documentation
Zenty Documentation
The powerful Zustand-based library that simplifies CRUD operations with elegant state management
Installation
Get started with Zenty in seconds
npm
1npm install zenty zustand
yarn
1yarn add zenty zustand
pnpm
1pnpm add zenty zustand
Quick Start
Get up and running with Zenty in under 2 minutes
1
Managing Collections with createEntitiesStore
ProductList.tsx
1import { createEntitiesStore } from 'zenty';
2
3interface Product {
4 id: string;
5 name: string;
6 price: number;
7 category: string;
8}
9
10// Create a store for managing products
11const useProductStore = createEntitiesStore<Product>();
12
13// Use in your React component
14function ProductList() {
15 const {
16 entities,
17 loading,
18 error,
19 add,
20 update,
21 delete: deleteProduct
22 } = useProductStore();
23
24 const handleAddProduct = () => {
25 add({
26 id: crypto.randomUUID(),
27 name: 'New Product',
28 price: 29.99,
29 category: 'Electronics'
30 });
31 };
32
33 return (
34 <div>
35 {loading && <p>Loading...</p>}
36 {error && <p>Error: {error}</p>}
37
38 <button onClick={handleAddProduct}>Add Product</button>
39
40 {entities.map(product => (
41 <div key={product.id}>
42 <h3>{product.name}</h3>
43 <p>Price: ${product.price}</p>
44 <button onClick={() => deleteProduct(product.id)}>
45 Delete
46 </button>
47 </div>
48 ))}
49 </div>
50 );
51}
2
Managing Single Entities with createEntityStore
UserProfile.tsx
1import { createEntityStore } from 'zenty';
2
3interface User {
4 id: string;
5 name: string;
6 email: string;
7 preferences: {
8 theme: 'light' | 'dark';
9 notifications: boolean;
10 };
11}
12
13// Create a store for managing a single user
14const useUserStore = createEntityStore<User>({
15 deepMerge: true // Enable deep merging for nested objects
16});
17
18function UserProfile() {
19 const { entity: user, loading, set, update } = useUserStore();
20
21 const updateTheme = (theme: 'light' | 'dark') => {
22 update({
23 preferences: {
24 theme
25 }
26 });
27 };
28
29 return (
30 <div>
31 {user && (
32 <div>
33 <h2>{user.name}</h2>
34 <p>{user.email}</p>
35 <button onClick={() => updateTheme('dark')}>
36 Switch to Dark Mode
37 </button>
38 </div>
39 )}
40 </div>
41 );
42}
That's it! You now have a fully functional store with CRUD operations, loading states, error handling, and TypeScript support.
API Reference
Complete reference for all Zenty functions and methods
createEntitiesStore<T>()
CollectionsCreates a Zustand store for managing collections of entities with built-in CRUD operations.
Signature
1// For entities with 'id' field
2function createEntitiesStore<T extends { id: string | number }>(
3 options?: EntitiesStoreOptions<T, 'id'>
4): UseBoundStore<StoreApi<EntitiesState<T, 'id'> & EntitiesActions<T, 'id'>>>;
5
6// For entities with custom ID field
7function createEntitiesStore<T, K extends keyof T>(
8 options: EntitiesStoreOptions<T, K>
9): UseBoundStore<StoreApi<EntitiesState<T, K> & EntitiesActions<T, K>>>;
Options
1interface EntitiesStoreOptions<T, K extends keyof T> {
2 idKey: K; // Which field is the unique identifier
3 initialState?: T[]; // Initial list of entities (defaults to [])
4
5 // Optional custom implementations for each action
6 add?: (item: T, state: EntitiesState<T, K>) => T[];
7 addMany?: (items: T[], state: EntitiesState<T, K>) => T[];
8 update?: (uid: T[K], patch: Partial<T>, state: EntitiesState<T, K>) => T[];
9 updateMany?: (patches: (Pick<T, K> & Partial<T>)[], state: EntitiesState<T, K>) => T[];
10 delete?: (uid: T[K], state: EntitiesState<T, K>) => T[];
11 deleteMany?: (uids: T[K][], state: EntitiesState<T, K>) => T[];
12}
State Properties
1interface EntitiesState<T, K extends keyof T> {
2 entities: T[]; // Array of entities
3 loaded: boolean; // Whether data has been loaded
4 loading: boolean; // Whether an operation is in progress
5 error: string | null; // Error message if any
6}
Actions
1interface EntitiesActions<T, K extends keyof T> {
2 // CRUD Operations
3 add: (item: T) => void;
4 addMany: (items: T[]) => void;
5 update: (uid: T[K], patch: Partial<T>) => void;
6 updateMany: (patches: (Pick<T, K> & Partial<T>)[]) => void;
7 delete: (uid: T[K]) => void;
8 deleteMany: (uids: T[K][]) => void;
9
10 // Utility Functions
11 clear: () => void;
12 find: (uid: T[K]) => T | undefined;
13 has: (uid: T[K]) => boolean;
14 replaceAll: (items: T[]) => void;
15
16 // State Helpers
17 setError: (error: string | null) => void;
18 setLoading: (loading: boolean) => void;
19}
createEntityStore<T>()
Single EntityCreates a Zustand store for managing a single entity with set, update, and clear operations.
Signature
1function createEntityStore<T>(
2 options?: EntityStoreOptions<T>
3): UseBoundStore<StoreApi<EntityState<T> & EntityActions<T>>>;
Options & State
1interface EntityStoreOptions<T> {
2 initialState?: T | null; // Initial entity state
3 deepMerge?: boolean; // Whether updates should deep merge
4}
5
6interface EntityState<T> {
7 entity: T | null; // The entity or null
8 loaded: boolean; // Whether entity has been loaded
9 loading: boolean; // Whether an operation is in progress
10 error: string | null; // Error message if any
11}
Actions
1interface EntityActions<T> {
2 set: (entity: T) => void; // Set the entire entity
3 update: (updated: Partial<T>) => void; // Update parts of the entity
4 clear: () => void; // Clear the entity
5 setError: (error: string | null) => void; // Set error state
6 setLoading: (loading: boolean) => void; // Set loading state
7}
Advanced Examples
Real-world usage patterns and advanced features
Custom ID Field
custom-id-field.ts
1interface User {
2 userId: string;
3 name: string;
4 email: string;
5}
6
7const useUserStore = createEntitiesStore<User, 'userId'>({
8 idKey: 'userId',
9 initialState: [
10 { userId: '1', name: 'John', email: 'john@example.com' }
11 ]
12});
Custom CRUD Operations
custom-crud.ts
1const useProductStore = createEntitiesStore<Product>({
2 // Custom add logic with validation
3 add: (item, state) => {
4 if (state.entities.some(p => p.name === item.name)) {
5 throw new Error('Product name must be unique');
6 }
7 return [...state.entities, { ...item, createdAt: new Date() }];
8 },
9
10 // Custom delete with soft delete
11 delete: (id, state) => {
12 return state.entities.map(p =>
13 p.id === id ? { ...p, deleted: true } : p
14 );
15 }
16});
Deep Merging
deep-merge.ts
1interface Settings {
2 ui: {
3 theme: string;
4 language: string;
5 };
6 notifications: {
7 email: boolean;
8 push: boolean;
9 };
10}
11
12const useSettingsStore = createEntityStore<Settings>({
13 deepMerge: true,
14 initialState: {
15 ui: { theme: 'light', language: 'en' },
16 notifications: { email: true, push: false }
17 }
18});
19
20// This will only update the theme, keeping language intact
21const { update } = useSettingsStore();
22update({ ui: { theme: 'dark' } });
Async Operations
async-operations.ts
1const useProductStore = createEntitiesStore<Product>();
2
3async function fetchProducts() {
4 const { setLoading, setError, replaceAll } = useProductStore.getState();
5
6 try {
7 setLoading(true);
8 const response = await fetch('/api/products');
9 const products = await response.json();
10 replaceAll(products);
11 } catch (error) {
12 setError(error.message);
13 } finally {
14 setLoading(false);
15 }
16}
17
18async function createProduct(product: Omit<Product, 'id'>) {
19 const { setLoading, setError, add } = useProductStore.getState();
20
21 try {
22 setLoading(true);
23 const response = await fetch('/api/products', {
24 method: 'POST',
25 body: JSON.stringify(product)
26 });
27 const newProduct = await response.json();
28 add(newProduct);
29 } catch (error) {
30 setError(error.message);
31 } finally {
32 setLoading(false);
33 }
34}
Real-World Example: Complete Todo App
See how easy it is to build a complete todo application with Zenty - just a few lines of code!
This example shows a complete todo app with categories, filtering, and persistence - all powered by Zenty's simple API.
1
Define Your Types
types/todo.ts
1// types/todo.ts
2interface Todo {
3 id: number;
4 title: string;
5 completed: boolean;
6 category: 'work' | 'personal' | 'shopping';
7 priority: 'low' | 'medium' | 'high';
8 dueDate?: Date;
9 createdAt: Date;
10}
11
12interface TodoFilters {
13 category: string;
14 showCompleted: boolean;
15 priority: string;
16}
2
Create Your Stores (2 lines!)
stores/todo.ts
1// stores/todo.ts
2import { createEntitiesStore, createEntityStore } from 'zenty';
3
4// All todos with full CRUD operations
5export const useTodoStore = createEntitiesStore<Todo>();
6
7// Filter settings
8export const useFilterStore = createEntityStore<TodoFilters>({
9 initialState: {
10 category: 'all',
11 showCompleted: true,
12 priority: 'all'
13 }
14});
3
Build Your Todo App Component
components/TodoApp.tsx
1// components/TodoApp.tsx
2import { useTodoStore, useFilterStore } from '@/stores/todo';
3
4export function TodoApp() {
5 const {
6 entities: todos,
7 add,
8 update,
9 delete: deleteTodo,
10 loading,
11 error
12 } = useTodoStore();
13
14 const {
15 entity: filters,
16 update: updateFilters
17 } = useFilterStore();
18
19 // Add new todo
20 const handleAddTodo = (title: string, category: Todo['category']) => {
21 add({
22 id: Date.now(),
23 title,
24 completed: false,
25 category,
26 priority: 'medium',
27 createdAt: new Date()
28 });
29 };
30
31 // Toggle completion
32 const handleToggleComplete = (id: number) => {
33 const todo = todos.find(t => t.id === id);
34 if (todo) {
35 update(id, { completed: !todo.completed });
36 }
37 };
38
39 // Filter todos based on current filters
40 const filteredTodos = todos.filter(todo => {
41 if (filters.category !== 'all' && todo.category !== filters.category) return false;
42 if (!filters.showCompleted && todo.completed) return false;
43 if (filters.priority !== 'all' && todo.priority !== filters.priority) return false;
44 return true;
45 });
46
47 // Statistics
48 const stats = {
49 total: todos.length,
50 completed: todos.filter(t => t.completed).length,
51 pending: todos.filter(t => !t.completed).length,
52 highPriority: todos.filter(t => t.priority === 'high' && !t.completed).length
53 };
54
55 return (
56 <div className="max-w-4xl mx-auto p-6">
57 <header className="mb-8">
58 <h1 className="text-3xl font-bold mb-2">My Todo App</h1>
59 <div className="flex gap-4 text-sm text-muted-foreground">
60 <span>Total: {stats.total}</span>
61 <span>Completed: {stats.completed}</span>
62 <span>Pending: {stats.pending}</span>
63 <span className="text-red-500">High Priority: {stats.highPriority}</span>
64 </div>
65 </header>
66
67 {/* Add Todo Form */}
68 <TodoForm onAdd={handleAddTodo} />
69
70 {/* Filters */}
71 <TodoFilters
72 filters={filters}
73 onUpdateFilters={updateFilters}
74 />
75
76 {/* Todo List */}
77 {loading && <div>Loading todos...</div>}
78 {error && <div className="text-red-500">Error: {error}</div>}
79
80 <div className="space-y-2">
81 {filteredTodos.map(todo => (
82 <div key={todo.id} className="flex items-center gap-3 p-3 border rounded">
83 <input
84 type="checkbox"
85 checked={todo.completed}
86 onChange={() => handleToggleComplete(todo.id)}
87 />
88 <span className={todo.completed ? 'line-through' : ''}>
89 {todo.title}
90 </span>
91 <button
92 onClick={() => deleteTodo(todo.id)}
93 className="ml-auto text-red-500"
94 >
95 Delete
96 </button>
97 </div>
98 ))}
99 </div>
100
101 {filteredTodos.length === 0 && (
102 <div className="text-center py-12 text-muted-foreground">
103 No todos found. Add some tasks to get started!
104 </div>
105 )}
106 </div>
107 );
108}
What You Get With Zenty
- • Complete CRUD operations in 2 lines of code
- • Automatic state management (loading, error, loaded)
- • Built-in filtering and search capabilities
- • Type-safe operations with full TypeScript support
- • Optimistic updates and error handling
- • Zero boilerplate - just focus on your UI
Advanced Features Available
- • Bulk operations (addMany, updateMany, deleteMany)
- • Advanced search with find() and has() methods
- • State persistence with localStorage integration
- • Real-time updates and synchronization
- • Custom validation and error handling
- • Performance optimizations built-in
Result: A complete, production-ready todo application with just ~100 lines of code instead of 500+ lines with traditional state management!
Best Practices
Recommended patterns and practices for using Zenty effectively
1Entity Design
Design your entities with clear, consistent ID fields and optional properties.
1// ✅ Good: Clear ID field, optional properties
2interface Product {
3 id: number // Required, unique identifier
4 name: string // Required fields
5 price?: number // Optional fields with ?
6 category?: string
7 createdAt?: Date
8}
9
10// ❌ Avoid: Unclear ID field, all required
11interface Product {
12 productId: string // Unclear naming
13 name: string
14 price: number // Should be optional
15 category: string // Should be optional
16}
2Error Handling
Always handle loading and error states in your components.
1function ProductList() {
2 const { entities, loading, error, setError } = useProductStore()
3
4 // ✅ Handle all states
5 if (loading) return <LoadingSpinner />
6 if (error) return <ErrorMessage error={error} onDismiss={() => setError(null)} />
7 if (entities.length === 0) return <EmptyState />
8
9 return (
10 <div>
11 {entities.map(product => (
12 <ProductCard key={product.id} product={product} />
13 ))}
14 </div>
15 )
16}
3Async Operations
Use the built-in loading states for async operations.
1async function fetchAndAddProducts() {
2 const { addMany, setLoading, setError } = useProductStore()
3
4 try {
5 setLoading(true)
6 setError(null)
7 const response = await fetch('/api/products')
8 const products = await response.json()
9 addMany(products)
10 } catch (error) {
11 setError('Failed to fetch products')
12 } finally {
13 setLoading(false)
14 }
15}
4Store Organization
Organize your stores in separate files for better maintainability.
1// stores/products.ts
2export const useProductStore = createEntitiesStore<Product>()
3
4// stores/users.ts
5export const useUserStore = createEntitiesStore<User>()
6
7// stores/settings.ts
8export const useSettingsStore = createEntityStore<Settings>({
9 initialState: { theme: 'light', notifications: true }
10})
11
12// stores/index.ts
13export * from './products'
14export * from './users'
15export * from './settings'
Migration Guide
How to migrate from traditional Zustand to Zenty
Migration is straightforward and can be done incrementally. You can use Zenty alongside existing Zustand stores.
Step-by-Step Migration:
1
Install Zenty
1npm install zenty
2
Replace your existing store
❌ Before (50+ lines)
1const useProductStore = create((set) => ({
2 products: [],
3 loading: false,
4 error: null,
5 addProduct: (product) => set((state) => ({
6 products: [...state.products, product]
7 })),
8 updateProduct: (id, updates) => set((state) => ({
9 products: state.products.map(p =>
10 p.id === id ? { ...p, ...updates } : p
11 )
12 })),
13 deleteProduct: (id) => set((state) => ({
14 products: state.products.filter(p => p.id !== id)
15 })),
16 // ... 40+ more lines
17}))
✅ After (1 line)
1const useProductStore = createEntitiesStore<Product>()
3
Update your component usage
1// Before
2const { products, addProduct, loading } = useProductStore()
3
4// After (same destructuring, different method names)
5const { entities: products, add: addProduct, loading } = useProductStore()
4
Test and deploy
Your app should work exactly the same, but with less code and more features!
Support & Resources
Get help and stay updated with Zenty