Context-aware state management built on top of Zustand
The same bear, now with context superpowers
A small, fast and scalable context-aware state management solution built on top of Zustand. It solves one key problem: using the same store with different states in multiple component trees.
- 📦 Multiple instances of the same store
- 🌲 Hierarchical state inheritance across components
- 🎯 Named store references
- 🔄 Same API as Zustand, just with context awareness
- 🔌 Full compatibility with Zustand middleware
# Using npm
npm install @mag1yar/zustand-context
# Using yarn
yarn add @mag1yar/zustand-context
# Using pnpm
pnpm add @mag1yar/zustand-context
// 1. Create a store with context awareness
import { create } from '@mag1yar/zustand-context';
type CounterState = {
count: number;
increment: () => void;
reset: () => void;
};
const useCounterStore = create<CounterState>(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
reset: () => set({ count: 0 }),
}),
{
name: 'Counter', // Required: name for your store context
},
);
// 2. Wrap your components with Provider
function App() {
return (
<useCounterStore.Provider initialState={{ count: 10 }}>
<Counter />
</useCounterStore.Provider>
);
}
// 3. Use it just like regular Zustand
function Counter() {
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
</div>
);
}
function App() {
return (
<useCounterStore.Provider instanceId="app" initialState={{ count: 100 }}>
{/* Root Counter */}
<Counter label="App Counter" />
{/* First nested counter */}
<div className="section">
<useCounterStore.Provider instanceId="section1" initialState={{ count: 5 }}>
<Counter label="Section 1" />
<MultiCounterInfo />
</useCounterStore.Provider>
</div>
{/* Second nested counter */}
<div className="section">
<useCounterStore.Provider instanceId="section2" initialState={{ count: 10 }}>
<Counter label="Section 2" />
<MultiCounterInfo />
</useCounterStore.Provider>
</div>
</useCounterStore.Provider>
);
}
// Counter uses the nearest provider automatically
function Counter({ label }) {
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
return (
<div>
<h3>
{label}: {count}
</h3>
<button onClick={increment}>Increment</button>
</div>
);
}
// Access multiple counters using the from option
function MultiCounterInfo() {
// Gets the current section's count
const sectionCount = useCounterStore((state) => state.count);
// Gets the app-level count from the parent
const appCount = useCounterStore((state) => state.count, { from: 'app' });
return (
<div className="info">
<p>Section count: {sectionCount}</p>
<p>App total: {appCount}</p>
</div>
);
}
Challenge | React Context | Zustand | zustand-context |
---|---|---|---|
Component-specific state | ✅ Good | ❌ Global only | ✅ Best |
Avoiding re-renders | ❌ Poor | ✅ Good | ✅ Good |
Multiple instances | ✅ Good | ✅ Best | |
Access specific instances | ❌ No direct access | ❌ Not applicable | ✅ Simple with options |
State inheritance | ❌ Not applicable | ✅ Automatic hierarchical | |
Middleware support | ❌ None | ✅ Excellent | ✅ Compatible |
Creates a context-aware Zustand store.
const useStore = create(
(set, get) => ({
// state and actions
}),
{
name: 'MyStore', // Required: unique name for the context
strict: true, // Optional: throws if Provider is missing (default: true)
debug: false, // Optional: enables debug logging (default: false)
onError: (error) => {}, // Optional: custom error handler
},
);
The hook returned by create
allows you to access the state from the nearest provider.
// Get the full state
const state = useStore();
// Get a slice of state
const count = useStore((state) => state.count);
// Access a specific named instance
const otherCount = useStore((state) => state.count, { from: 'InstanceName' });
Each store has a Provider component for creating instances.
<useStore.Provider
instanceId="uniqueName" // Optional: identifier for this instance
initialState={
{
/* initial values */
}
} // Optional: override initial state
>
{children}
</useStore.Provider>
zustand-context supports all Zustand middleware through the adaptMiddleware
utility.
The library includes an adapted version of the persist middleware that works with multiple store instances:
import { create } from '@mag1yar/zustand-context';
import { persist } from '@mag1yar/zustand-context/middleware';
const useProfileStore = create(
persist(
(set) => ({
username: 'Guest',
theme: 'light',
setUsername: (name) => set({ username: name }),
}),
{ name: 'user-profile' }
),
{ name: 'Profile' }
);
// Default instance won't persist
<useProfileStore.Provider>
<Profile />
</useProfileStore.Provider>
// Named instances will persist state
<useProfileStore.Provider instanceId="user1">
<Profile />
</useProfileStore.Provider>
See the persist middleware documentation for details on advanced features and configuration options.
For complete API reference, advanced configuration, middleware support, and more examples: https://mag1yar.github.io/zustand-context/
MIT