npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@oxog/state

v1.2.0

Published

Zero-dependency reactive state management for any framework - featuring slices, computed values, effects, validation, and middleware support

Readme

@oxog/state

npm version Bundle Size License: MIT

Zero-dependency reactive state management for any framework.

Features

  • Zero Dependencies - No runtime dependencies, smaller bundle size
  • Framework Agnostic - Works with React, Vue, Svelte, or vanilla JS
  • TypeScript Native - Built with strict mode, full type inference
  • Plugin System - Extend with persist, devtools, history, sync, logger, effects, validate
  • Slices & Computed - Modular state organization with derived values
  • Store Federation - Combine multiple stores for large applications
  • Tiny Bundle - Less than 2KB gzipped for core

Installation

npm install @oxog/state
yarn add @oxog/state
pnpm add @oxog/state

Quick Start

import { createStore, useStore } from '@oxog/state';

// Create a store with state and actions
const store = createStore({
  count: 0,
  $increment: (state) => ({ count: state.count + 1 }),
  $decrement: (state) => ({ count: state.count - 1 }),
});

// Use in React
function Counter() {
  const count = useStore(store, (s) => s.count);
  const increment = useStore(store, (s) => s.increment);

  return <button onClick={increment}>{count}</button>;
}

// Use in vanilla JS
store.subscribe((state) => console.log(state.count));
store.getState().increment();

API Reference

Core

createStore(initialState)

Creates a reactive store.

const store = createStore({
  count: 0,
  $increment: (state) => ({ count: state.count + 1 }),
});

batch(fn)

Batch multiple updates into a single notification.

import { batch } from '@oxog/state';

batch(() => {
  store.setState({ count: 1 });
  store.setState({ name: 'John' });
}); // Single notification

Store Methods

| Method | Description | |--------|-------------| | getState() | Get current state | | setState(partial) | Update state with partial object | | merge(partial) | Deep merge state | | reset() | Reset to initial state | | subscribe(listener, selector?) | Subscribe to changes | | use(plugin, options?) | Add plugin | | destroy() | Cleanup store |

React Hooks

useStore(store, selector?, equalityFn?)

Subscribe to store state in React components.

// Full state
const state = useStore(store);

// With selector
const count = useStore(store, (s) => s.count);

// With custom equality
const user = useStore(store, (s) => s.user, deepEqual);

useShallow(selector) v1.2

Wrap selectors returning objects to use shallow equality comparison.

import { useStore, useShallow } from '@oxog/state';

// Without useShallow - re-renders on any state change
// const data = useStore(store, s => ({ a: s.a, b: s.b }));

// With useShallow - only re-renders when values change
const data = useStore(store, useShallow(s => ({ a: s.a, b: s.b })));

useStoreSelector(store, selectors) v1.2

Select multiple values with independent subscriptions.

import { useStoreSelector } from '@oxog/state';

const { userCount, totalRevenue } = useStoreSelector(store, {
  userCount: s => s.users.length,
  totalRevenue: s => s.orders.reduce((sum, o) => sum + o.total, 0),
});

useStoreActions(store, ...actionNames) v1.2

Get multiple actions with stable references.

import { useStoreActions } from '@oxog/state';

const { increment, decrement, reset } = useStoreActions(
  store,
  'increment', 'decrement', 'reset'
);

useSetState(store) v1.2

Get a stable setState function for partial updates.

import { useSetState } from '@oxog/state';

function Form() {
  const setState = useSetState(store);

  return (
    <input onChange={(e) => setState({ name: e.target.value })} />
  );
}

useTransientSubscribe(store, selector, callback) v1.2

Subscribe to state changes without causing re-renders.

import { useTransientSubscribe } from '@oxog/state';

function Analytics() {
  useTransientSubscribe(
    store,
    s => s.pageViews,
    (views) => analytics.track('page_view', { count: views })
  );

  return <div>Tracking active</div>;
}

useCreateStore(initialState)

Create a store scoped to component lifecycle.

function MyComponent() {
  const store = useCreateStore({ count: 0 });
  // Store is destroyed when component unmounts
}

useAction(store, actionName)

Get a stable action reference.

const increment = useAction(store, 'increment');

Patterns v1.2

Slices

Organize state into modular, reusable slices.

import { createStore, createSlice } from '@oxog/state';

const userSlice = createSlice('user', {
  name: '',
  email: '',
  $login: (state, name: string, email: string) => ({ name, email }),
  $logout: () => ({ name: '', email: '' }),
});

const cartSlice = createSlice('cart', {
  items: [],
  $addItem: (state, item) => ({ items: [...state.items, item] }),
});

const store = createStore({
  ...userSlice,
  ...cartSlice,
});

// Access: store.getState().user.name
// Action: store.getState().user.login('John', '[email protected]')

Computed Values

Derive values from state with automatic memoization.

import { createStore, computed } from '@oxog/state';

const store = createStore({
  items: [{ price: 10, qty: 2 }, { price: 20, qty: 1 }],

  totalItems: computed((state) =>
    state.items.reduce((sum, i) => sum + i.qty, 0)
  ),

  totalPrice: computed((state) =>
    state.items.reduce((sum, i) => sum + i.price * i.qty, 0)
  ),
});

console.log(store.getState().totalItems); // 3
console.log(store.getState().totalPrice); // 40

Store Federation

Combine multiple stores into a unified interface.

import { createStore, createFederation } from '@oxog/state';

const userStore = createStore({ name: 'John' });
const cartStore = createStore({ items: [] });
const settingsStore = createStore({ theme: 'dark' });

const federation = createFederation({
  user: userStore,
  cart: cartStore,
  settings: settingsStore,
});

// Access all stores
const state = federation.getState();
console.log(state.user.name, state.cart.items, state.settings.theme);

// Subscribe to specific store
federation.subscribeStore('user', (userState) => {
  console.log('User changed:', userState);
});

Plugins

Persist

Persist state to localStorage with advanced options.

import { persist } from '@oxog/state';

const store = createStore({ count: 0, temp: '' })
  .use(persist({
    key: 'my-app',
    storage: localStorage,
    // v1.2.0 options
    version: 1,
    migrate: (state, version) => state,
    partialize: (state) => ({ count: state.count }), // Only persist count
    writeDebounce: 100,
    onRehydrateStorage: (state) => console.log('Hydrated:', state),
  }));

DevTools

Connect to Redux DevTools Extension.

import { devtools } from '@oxog/state';

const store = createStore({ count: 0 })
  .use(devtools({ name: 'My Store' }));

History

Add undo/redo functionality.

import { history } from '@oxog/state';

const store = createStore({ count: 0 })
  .use(history({ limit: 50 }));

store.setState({ count: 1 });
store.setState({ count: 2 });
store.undo(); // { count: 1 }
store.redo(); // { count: 2 }

Sync

Synchronize state across browser tabs.

import { sync } from '@oxog/state';

const store = createStore({ count: 0 })
  .use(sync({ channel: 'my-app' }));

Logger v1.2

Development logging with diff, timestamps, and filtering.

import { logger } from '@oxog/state';

const store = createStore({ count: 0 })
  .use(logger({
    level: 'debug',
    collapsed: true,
    diff: true,
    timestamp: true,
    filter: (action, state) => state.count > 0,
  }));

Effects v1.2

Reactive side effects with debounce and cleanup.

import { effects, createEffect } from '@oxog/state';

const store = createStore({
  searchTerm: '',
  results: [],
})
.use(effects({
  search: createEffect(
    (state) => state.searchTerm,
    async (term, { setState, signal }) => {
      const res = await fetch(`/api/search?q=${term}`, { signal });
      const results = await res.json();
      setState({ results });
    },
    { debounce: 300 }
  ),
}));

Validate v1.2

Schema-agnostic validation with Zod, Yup, or custom validators.

import { validate } from '@oxog/state';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  age: z.number().min(0).max(150),
});

const store = createStore({ email: '', age: 0 })
  .use(validate({
    schema,
    timing: 'before',
    rejectInvalid: true,
    onError: (errors) => console.error(errors),
  }));

Async Actions

Handle async operations with async actions.

const store = createStore({
  data: null,
  loading: false,
  error: null,
  $fetch: async (state, url: string) => {
    store.setState({ loading: true, error: null });
    try {
      const res = await fetch(url);
      const data = await res.json();
      return { data, loading: false };
    } catch (error) {
      return { error, loading: false };
    }
  },
});

await store.getState().fetch('/api/users');

TypeScript

Full TypeScript support with type inference.

import { createStore, createSlice, InferSliceState } from '@oxog/state';

// Type inference from slices
const counterSlice = createSlice('counter', {
  count: 0,
  $increment: (state) => ({ count: state.count + 1 }),
});

type CounterState = InferSliceState<typeof counterSlice>;
// { counter: { count: number } }

// Explicit types
interface State {
  count: number;
  user: User | null;
}

const store = createStore<State>({
  count: 0,
  user: null,
  $increment: (s) => ({ count: s.count + 1 }),
  $setUser: (s, user: User) => ({ user }),
});

Testing v1.2

Testing utilities for stores.

import { createTestStore, mockStore, getStoreSnapshot } from '@oxog/state/testing';

// Isolated test store
const store = createTestStore({
  count: 0,
  $increment: (s) => ({ count: s.count + 1 }),
});

// Mock store with action tracking
const mock = mockStore({ count: 0, $increment: (s) => ({ count: s.count + 1 }) });
mock.getState().increment();
expect(mock.actionCalls.increment).toBe(1);

// Snapshot testing
const snapshot = getStoreSnapshot(store);
expect(snapshot).toMatchSnapshot();

Browser Support

  • Chrome 60+
  • Firefox 55+
  • Safari 12+
  • Edge 79+

Documentation

Full documentation available at state.oxog.dev

License

MIT