npm package: @melodyleonard/react-context-toolkit
Description: Typed React Context store creator and provider composer
Latest version: 0.1.0

# React Context Toolkit

Typed, minimal helpers for shaping React Context into ergonomic, composable stores — plus a simple way to nest multiple providers without noise.

Why
- You like Context for simple app state but want ergonomic hooks and strong typing
- You often end up stacking providers (Theme, Auth, Feature flags, etc.) and want a tidy, declarative composition
- You need optional persistence (local/session/custom) without pulling in a heavy state manager

Key features
- makeStore<S, A>: create a typed Context store and hooks
  - Returns { Provider, useStore, useDispatch }
  - Strong generics and zero any
  - Optional persistence with a pluggable StorageAdapter
- composeProviders([...Providers]): turn N providers into one
  - Clean App wrappers with a single composed provider
- SSR-safe persistence: localStorage only used when window is available

Installation

```bash
# npm
npm install @melodyleonard/react-context-toolkit

# yarn
yarn add @melodyleonard/react-context-toolkit

# pnpm
pnpm add @melodyleonard/react-context-toolkit
```

Compatibility
- React 18+ (works with React 19)
- TypeScript recommended (strongly typed generics)
- Works in Vite, CRA, Next.js, etc.

Quick start

1) Create a store (Theme)

```tsx
import { makeStore } from '@melodyleonard/react-context-toolkit';

type ThemeState = { mode: 'light' | 'dark' };
type ThemeAction = { type: 'TOGGLE_THEME' };

const initial_state: ThemeState = { mode: 'light' };

export const {
  useStore: useThemeStore,
  useDispatch: useThemeDispatch,
  Provider: ThemeProvider,
} = makeStore<ThemeState, ThemeAction>(
  'theme',
  initial_state,
  (state, action) => {
    switch (action.type) {
      case 'TOGGLE_THEME':
        return { mode: state.mode === 'light' ? 'dark' : 'light' };
      default:
        return state;
    }
  },
  { persist: true },
);
```

2) Create a store (Auth)

```tsx
import { makeStore } from '@melodyleonard/react-context-toolkit';

type AuthState = { user: string | null };
type AuthAction = { type: 'LOGIN'; payload: string } | { type: 'LOGOUT' };

const initial_state: AuthState = { user: null };

export const {
  useStore: useAuthStore,
  useDispatch: useAuthDispatch,
  Provider: AuthProvider,
} = makeStore<AuthState, AuthAction>(
  'auth',
  initial_state,
  (state, action) => {
    switch (action.type) {
      case 'LOGIN':
        return { user: action.payload };
      case 'LOGOUT':
        return { user: null };
      default:
        return state;
    }
  },
  { persist: true },
);
```

3) Compose providers once

```tsx
// src/Providers.tsx
import { composeProviders } from '@melodyleonard/react-context-toolkit';
import { ThemeProvider } from './store/themeStore';
import { AuthProvider } from './store/authStore';

const Providers = composeProviders([ThemeProvider, AuthProvider]);
export default Providers;
```

4) Use in App

```tsx
// src/App.tsx
import Providers from './Providers';
import ExampleComponent from './components/ExampleComponent';

export default function App() {
  return (
    <Providers>
      <ExampleComponent />
    </Providers>
  );
}
```

5) Use the hooks anywhere

```tsx
// src/components/ExampleComponent.tsx
import { useThemeStore, useThemeDispatch } from '../store/themeStore';
import { useAuthStore, useAuthDispatch } from '../store/authStore';

export default function ExampleComponent() {
  const theme = useThemeStore();
  const themeDispatch = useThemeDispatch();
  const auth = useAuthStore();
  const authDispatch = useAuthDispatch();

  return (
    <div style={{
      background: theme.mode === 'light' ? '#fff' : '#333',
      color: theme.mode === 'light' ? '#000' : '#fff',
    }}>
      <p>Theme: {theme.mode}</p>
      <button onClick={() => themeDispatch({ type: 'TOGGLE_THEME' })}>Toggle Theme</button>

      <p>User: {auth.user ?? 'Not logged in'}</p>
      <button onClick={() => authDispatch({ type: 'LOGIN', payload: 'user@example.com' })}>Login</button>
      <button onClick={() => authDispatch({ type: 'LOGOUT' })}>Logout</button>
    </div>
  );
}
```

Persistence (StorageAdapter)

You can plug in any storage mechanism. By default, localStorage is used when available (SSR-safe).

```ts
import type { StorageAdapter } from '@melodyleonard/react-context-toolkit';

const sessionAdapter: StorageAdapter<MyState> = {
  get: (key) => {
    try {
      const raw = sessionStorage.getItem(key);
      return raw ? (JSON.parse(raw) as MyState) : null;
    } catch {
      return null;
    }
  },
  set: (key, value) => {
    try {
      sessionStorage.setItem(key, JSON.stringify(value));
    } catch {
      // ignore write errors
    }
  },
};

const { Provider } = makeStore<MyState, MyAction>(
  'my-store',
  initial,
  reducer,
  { persist: { adapter: sessionAdapter } },
);
```

SSR notes
- Persistence writes happen in an effect — no localStorage access during SSR
- Use a custom adapter to read/write from cookies or server/session storage when needed

TypeScript overview
- S represents your store state shape
- A represents your union of actions
- useStore() returns S
- useDispatch() returns React.Dispatch<A>

FAQ
- Why Context, not Redux? Context is great for small/medium app state. This toolkit keeps it typed, minimal, and ergonomic. Use Redux or other tools if you need advanced devtools or middlewares.
- Can I share one store across routes/components? Yes — it’s a provider, so scope it wherever you need it shared.
- How do I avoid stale state across tabs? Use a storage adapter that syncs via storage events or add a small cross-tab sync.

Roadmap
- CLI to scaffold stores and re-compose providers automatically
- Optional devtools (action/state logging)
- More adapters (AsyncStorage for React Native)

License
MIT