Basic Usage
Learn the fundamentals of Zustic by creating and using your first store.
Core Concept
Zustic is built around a simple idea: stores are just functions that return state and actions.
Creating a Store
The create function is all you need:
import { create } from 'zustic';
const useStore = create((set) => ({
// Your state and actions here
}));
Simple Counter Store
Here's a basic counter example:
import { create } from 'zustic';
interface CounterStore {
count: number;
inc: () => void;
dec: () => void;
}
const useCounter = create<CounterStore>((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
dec: () => set((state) => ({ count: state.count - 1 })),
}));
Using the Store
Use the store just like any React hook:
import { useCounter } from './counterStore';
function Counter() {
const { count, inc, dec } = useCounter();
return (
<div>
<p>Count: {count}</p>
<button onClick={inc}>Increment</button>
<button onClick={dec}>Decrement</button>
</div>
);
}
State Updates
Method 1: Direct State Update
Pass an object with the new state:
const useStore = create((set) => ({
count: 0,
reset: () => set({ count: 0 }),
}));
Method 2: Functional Update
Use a function to access the previous state:
const useStore = create((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
}));
This is useful when your new state depends on the previous state.
Method 3: Using get to Access Current State
The get function allows you to read the current state anywhere:
const useStore = create((set, get) => ({
count: 0,
name: '',
// Use get to read current state
logState: () => {
const state = get();
console.log('Current state:', state);
},
// Compute derived values
getInfo: () => {
const state = get();
return `${state.name}: ${state.count}`;
},
// Access state in async operations
fetchData: async () => {
await delay(1000);
const currentState = get(); // Gets latest state
console.log('Count is:', currentState.count);
},
}));
Complex State Example
Let's create a more complex todo store:
import { create } from 'zustic';
interface Todo {
id: number;
text: string;
completed: boolean;
}
interface TodoStore {
todos: Todo[];
addTodo: (text: string) => void;
removeTodo: (id: number) => void;
toggleTodo: (id: number) => void;
clearCompleted: () => void;
}
const useTodoStore = create<TodoStore>((set) => {
let nextId = 1;
return {
todos: [],
addTodo: (text: string) =>
set((state) => ({
todos: [
...state.todos,
{ id: nextId++, text, completed: false },
],
})),
removeTodo: (id: number) =>
set((state) => ({
todos: state.todos.filter((todo) => todo.id !== id),
})),
toggleTodo: (id: number) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
),
})),
clearCompleted: () =>
set((state) => ({
todos: state.todos.filter((todo) => !todo.completed),
})),
};
});
Using the Todo Store
import { useTodoStore } from './todoStore';
function TodoApp() {
const { todos, addTodo, removeTodo, toggleTodo, clearCompleted } =
useTodoStore();
const handleAdd = () => {
const text = prompt('Enter todo:');
if (text) addTodo(text);
};
return (
<div>
<button onClick={handleAdd}>Add Todo</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => removeTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
<button onClick={clearCompleted}>Clear Completed</button>
</div>
);
}
Accessing Single State Values
You can destructure the store to access individual values:
const count = useCounter((state) => state.count);
const { count, inc } = useCounter();
State Best Practices
✅ Keep State Flat
// Good - flat structure
const useStore = create((set) => ({
userName: 'John',
userEmail: 'john@example.com',
userRole: 'admin',
}));
❌ Avoid Deep Nesting
// Bad - deeply nested
const useStore = create((set) => ({
user: {
profile: {
personal: {
name: 'John',
},
},
},
}));
✅ Use Immutable Updates
// Good - creates new array
set((state) => ({
items: [...state.items, newItem],
}));
// Bad - mutates existing array
set((state) => {
state.items.push(newItem);
return state;
});
Common Patterns
Toggle Boolean
const useUIStore = create((set) => ({
isOpen: false,
toggle: () => set((state) => ({ isOpen: !state.isOpen })),
}));
Array Operations
const useListStore = create((set) => ({
items: [] as string[],
// Add item
add: (item: string) =>
set((state) => ({
items: [...state.items, item],
})),
// Remove item
remove: (index: number) =>
set((state) => ({
items: state.items.filter((_, i) => i !== index),
})),
// Clear all
clear: () => set({ items: [] }),
}));
Nested Object Updates
const useUserStore = create((set) => ({
profile: { name: '', email: '', age: 0 },
updateProfile: (updates: Partial<Profile>) =>
set((state) => ({
profile: { ...state.profile, ...updates },
})),
}));
Next Steps
- Explore Advanced Examples
- Learn Best Practices
- Check the complete API Reference