@mag1yar/zustand-context
TypeScript icon, indicating that this package has built-in type declarations

0.4.0 • Public • Published

@mag1yar/zustand-context

Context-aware state management built on top of Zustand
The same bear, now with context superpowers

Version License

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.

Features

  • 📦 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

Installation

# Using npm
npm install @mag1yar/zustand-context

# Using yarn
yarn add @mag1yar/zustand-context

# Using pnpm
pnpm add @mag1yar/zustand-context

Basic Usage

// 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>
  );
}

Nested Contexts Example

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>
  );
}

Why zustand-context?

Challenge React Context Zustand zustand-context
Component-specific state ✅ Good ❌ Global only ✅ Best
Avoiding re-renders ❌ Poor ✅ Good ✅ Good
Multiple instances ✅ Good ⚠️ Requires custom setup ✅ Best
Access specific instances ❌ No direct access ❌ Not applicable ✅ Simple with options
State inheritance ⚠️ Manual prop passing ❌ Not applicable ✅ Automatic hierarchical
Middleware support ❌ None ✅ Excellent ✅ Compatible

API Reference

create

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
  },
);

useStore

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' });

Provider

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>

Middleware Support

zustand-context supports all Zustand middleware through the adaptMiddleware utility.

Persist Middleware

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.

Documentation

For complete API reference, advanced configuration, middleware support, and more examples: https://mag1yar.github.io/zustand-context/

License

MIT

Package Sidebar

Install

npm i @mag1yar/zustand-context

Weekly Downloads

8

Version

0.4.0

License

MIT

Unpacked Size

17.6 kB

Total Files

7

Last publish

Collaborators

  • magiyar