A lightweight, minimal state management library for React using useSyncExternalStore. Perfect for managing global state in React, React Native, and Next.js applications.
✨ Lightweight - Minimal footprint with zero dependencies (except React)
⚡ Simple API - Intuitive and easy to learn state management
🎣 React Hooks - Use hooks to access state in your components
📱 Multi-Platform - Works with React, React Native, and Next.js
🔄 Reactive Updates - Automatic re-renders on state changes
💾 TypeScript Support - Full TypeScript support with type safety
🚀 Production Ready - Optimized for performance and reliability
npm install zusticor with yarn:
yarn add zusticor with pnpm:
pnpm add zusticimport { create } from 'zustic';
type CounterStore = {
count: number;
inc: () => void;
dec: () => void;
reset: () => void;
};
export const useCounter = create<CounterStore>((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
dec: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));import { useCounter } from './store';
function Counter() {
const { count, inc, dec, reset } = useCounter();
return (
<div>
<p>Count: {count}</p>
<button onClick={inc}>Increment</button>
<button onClick={dec}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
}
export default Counter;Creates a new store with the given state and actions.
- initializer
(set: (partial: Partial<T> | ((state: T) => Partial<T>)) => void) => T- A function that receives the
setfunction and returns the initial state object - The
setfunction accepts either a partial state object or a function that takes the current state and returns a partial state object
- A function that receives the
A React hook function that provides access to the store state.
- T
extends object- The shape of your store state
import { create } from 'zustic';
// User store
export const useUserStore = create<UserStore>((set) => ({
user: null,
setUser: (user) => set({ user }),
clearUser: () => set({ user: null }),
}));
// Todos store
export const useTodosStore = create<TodosStore>((set) => ({
todos: [],
addTodo: (todo) => set((state) => ({
todos: [...state.todos, todo]
})),
removeTodo: (id) => set((state) => ({
todos: state.todos.filter(t => t.id !== id)
})),
}));
// Use both in a component
function App() {
const user = useUserStore();
const todos = useTodosStore();
return (
<>
<User />
<TodoList />
</>
);
}const useShopStore = create<ShopStore>((set) => ({
items: [],
cart: [],
total: 0,
addToCart: (item) => set((state) => ({
cart: [...state.cart, item],
total: state.total + item.price,
})),
removeFromCart: (itemId) => set((state) => ({
cart: state.cart.filter(item => item.id !== itemId),
total: state.total - state.cart.find(item => item.id === itemId)?.price || 0,
})),
clearCart: () => set({
cart: [],
total: 0,
}),
}));const useStatsStore = create<StatsStore>((set) => ({
scores: [],
addScore: (score) => set((state) => ({
scores: [...state.scores, score],
})),
// You can compute values directly in the component
// or create selector functions
getAverage: (state) => {
if (state.scores.length === 0) return 0;
return state.scores.reduce((a, b) => a + b, 0) / state.scores.length;
},
}));
function Stats() {
const { scores, addScore, getAverage } = useStatsStore();
const average = getAverage(useStatsStore());
return <div>Average: {average}</div>;
}// store/counterStore.ts
import { create } from 'zustic';
export const useCounterStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));// app/page.tsx
'use client';
import { useCounterStore } from '@/store/counterStore';
export default function Home() {
const { count, increment, decrement } = useCounterStore();
return (
<main>
<h1>Count: {count}</h1>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</main>
);
}import { create } from 'zustic';
import { View, Text, TouchableOpacity } from 'react-native';
const useThemeStore = create<ThemeStore>((set) => ({
isDark: false,
toggleTheme: () => set((state) => ({ isDark: !state.isDark })),
}));
function App() {
const { isDark, toggleTheme } = useThemeStore();
return (
<View style={{ backgroundColor: isDark ? '#000' : '#fff' }}>
<Text>{isDark ? 'Dark Mode' : 'Light Mode'}</Text>
<TouchableOpacity onPress={toggleTheme}>
<Text>Toggle Theme</Text>
</TouchableOpacity>
</View>
);
}Keep your stores organized in a dedicated directory:
src/
├── stores/
│ ├── counterStore.ts
│ ├── userStore.ts
│ └── index.ts
└── components/
Always define proper TypeScript types for better type safety:
interface CounterState {
count: number;
inc: () => void;
dec: () => void;
}
export const useCounter = create<CounterState>((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
dec: () => set((state) => ({ count: state.count - 1 })),
}));Try to keep your state structure as flat as possible for better performance:
// ❌ Avoid deeply nested structures
const state = { user: { profile: { settings: { theme: 'dark' } } } };
// ✅ Prefer flat structures
const state = { userTheme: 'dark' };Always return new objects instead of mutating state:
// ❌ Bad - mutating state
set((state) => {
state.items.push(newItem);
return state;
});
// ✅ Good - immutable updates
set((state) => ({
items: [...state.items, newItem],
}));- Minimize Subscriptions - Only subscribe to the parts of the state you need
- Use Memoization - Memoize components that depend on store state
- Avoid Large Objects - Split large stores into multiple smaller ones
- Batch Updates - Group related state updates together
Zustic works in all modern browsers that support ES6 and React 16.8+.
- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
- Mobile browsers supporting React Native
| Feature | Zustic | Zustand | Redux | Context API |
|---|---|---|---|---|
| Bundle Size | ~500B | ~2KB | ~7KB | - |
| Learning Curve | Very Easy | Easy | Hard | Medium |
| Boilerplate | Minimal | Minimal | Lots | Medium |
| DevTools | No | Yes | Yes | No |
| Middleware | No | Yes | Yes | No |
| TypeScript | ✅ | ✅ | ✅ | ✅ |
Make sure you're using the set function correctly. Always return a new object:
// ❌ Wrong
set({ count: state.count + 1 }); // state is undefined here
// ✅ Correct
set((state) => ({ count: state.count + 1 }));Ensure you're using the hook at the top level of your component:
// ❌ Bad
if (condition) {
const state = useStore();
}
// ✅ Good
const state = useStore();Before:
const CounterContext = createContext();
export function CounterProvider({ children }) {
const [count, setCount] = useState(0);
return (
<CounterContext.Provider value={{ count, setCount }}>
{children}
</CounterContext.Provider>
);
}
function useCounter() {
return useContext(CounterContext);
}After:
export const useCounter = create((set) => ({
count: 0,
setCount: (count) => set({ count }),
}));Before:
const counterSlice = createSlice({
name: 'counter',
initialState: { count: 0 },
reducers: {
increment: (state) => { state.count += 1; },
},
});
export const { increment } = counterSlice.actions;
export default useSelector((state) => state.counter);After:
export const useCounter = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));Contributions are welcome! Please feel free to submit a Pull Request.
ISC © 2024 Rejaul Karim
- Zustand - A small, fast and scalable bearbones state-management solution
- Jotai - Primitive and flexible state management for React
- Recoil - A state management library for React
- Initial release
- Basic state management with
createfunction - TypeScript support
- React, React Native, and Next.js compatibility