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

neutrix

v0.1.1

Published

A powerful and hopefully simple state management library for React.

Downloads

7

Readme

🌀 Neutrix

A powerful and hopefully simple state management library for React.

Docs!

Docs can be found here: https://duriantaco.github.io/neutrix

Table of Contents

Overview

Whether you’re building a small side project or a large-scale application, Neutrix’s flexible API helps you scale your state management without sacrificing developer experience.

Installation

npm install neutrix

or

yarn add neutrix

Motivation

The Problem

In React apps, global state vs. feature-specific state is a constant tension. Libraries often force one pattern:

Zustand-like: No providers, direct store usage (great for global, but not for isolation). Redux-like: Strictly uses Providers (great for large apps, but can feel overkill for small ones). Developers either wrap everything in Providers or never use them at all—both extremes can be suboptimal.

The Solution

Neutrix unifies both patterns into one library:

  • Simple, Hook-Only usage (like Zustand) with no Provider.
  • Provider usage (like Redux) for SSR, multiple stores, or advanced setups.

Choose whichever approach suits your current app scale—no separate libraries or big rewrites needed later.

Quick Decision Guide

  • Use “no Provider” if you have a single store and want minimal setup. Perfect for small/medium apps.

  • Use “with Provider” if you need multiple stores, SSR, or prefer the Redux-like pattern with DevTools scope.

If you want hook-only usage, you simply ignore the returned :

// Hook-only usage
export const { useStore, store } = createNeutrixStore({ count: 0 });

function Counter() {
  const count = useStore(s => s.count);
  // ...
}

If you want provider-based usage, you do:

export const { store, useStore, Provider } = createNeutrixStore({ count: 0 });

function App() {
  return (
    <Provider>
      <Counter />
    </Provider>
  );
}

Usage

1. Quick Start (Recommended)

The simplest approach: createNeutrixStore without a provider. No context, no overhead:

import { createNeutrixStore } from 'neutrix';

const { useStore } = createNeutrixStore({ count: 0 });

function Counter() {
  const count = useStore(s => s.count);
  return (
    <button onClick={() => useStore.store.set('count', count + 1)}>
      Count: {count}
    </button>
  );
}
  • Read state: useStore(s => s.something)
  • Write state: useStore.store.set('something', newValue) Done. No needed.

2. Single Store without provider

This is effectively the same as the Quick Start example, but more explicit. You can pass { provider: false } or omit it entirely:

import React from 'react';
import { createNeutrixStore } from 'neutrix';

interface AppState {
  user: null | { name: string };
  theme: 'light' | 'dark';
}

// returns a "hook-only" store by default
const { useStore, store } = createNeutrixStore<AppState>(
  { user: null, theme: 'light' },
  {
    name: 'appStore',
    // provider: false, // optional
  }
);

function Profile() {
  const user = useStore(s => s.user);
  return <div>{user?.name ?? 'Guest'}</div>;
}

function ThemeSwitcher() {
  const theme = useStore(s => s.theme);
  const toggleTheme = () =>
    store.set('theme', theme === 'light' ? 'dark' : 'light');

  return <button onClick={toggleTheme}>Current theme: {theme}</button>;
}

export default function App() {
  return (
    <div>
      <Profile />
      <ThemeSwitcher />
    </div>
  );
}

No , no context overhead, just a single store hook. Great for simpler apps.

3. Single Store With Provider

If you prefer a Provider pattern or need SSR, you can set provider: true:

import React from 'react';
import { createNeutrixStore } from 'neutrix';

interface AppState {
  user: null | { name: string };
  theme: 'light' | 'dark';
}

const { store, useStore, Provider } = createNeutrixStore<AppState>(
  { user: null, theme: 'light' },
  {
    provider: true, // <--- This creates a context-based store
    devTools: true
  }
);

function Profile() {
  const user = useStore(s => s.user);
  return <div>{user?.name ?? 'Guest'}</div>;
}

function ThemeSwitcher() {
  const theme = useStore(s => s.theme);
  const toggleTheme = () =>
    store.set('theme', theme === 'light' ? 'dark' : 'light');

  return <button onClick={toggleTheme}>Current theme: {theme}</button>;
}

export default function App() {
  return (
    <Provider>
      <Profile />
      <ThemeSwitcher />
    </Provider>
  );
}

Now, useStore reads from the context-based store. Perfect for larger apps, SSR, or when you need multiple store instances.

4. Multiple Stores

You can create multiple store instances (e.g. userStore, cartStore) and combine them under a single if you want:

import React from 'react';
import { NeutrixProvider, createNeutrixStore } from 'neutrix';

interface UserState {
  user: null | { name: string };
}
interface CartState {
  items: string[];
}

const { store: userStore, useStore: useUserStore } =
  createNeutrixStore<UserState>({ user: null }, { provider: true });

const { store: cartStore, useStore: useCartStore } =
  createNeutrixStore<CartState>({ items: [] }, { provider: true });

function App() {
  return (
    <NeutrixProvider stores={{ userStore, cartStore }}>
      <Profile />
      <Cart />
    </NeutrixProvider>
  );
}

function Profile() {
  const user = useUserStore(s => s.user);
  return <div>{user?.name ?? 'Guest'}</div>;
}

function Cart() {
  const items = useCartStore(s => s.items);
  return <div>Cart has {items.length} items</div>;
}

API Reference

createNeutrixStore

function createNeutrixStore<T extends State>(
  initialState: T,
  options?: StoreOptions & { provider?: boolean }
):
  | StoreHook<T> // if provider=false or omitted
  | { store: Store<T>, useStore: Function, Provider: React.FC } // if provider=true

Description:

Creates either a hook-based store (if provider: false or omitted) or a provider-based store (if provider: true). This is the recommended function for most users.

  • Parameters:

  • initialState: Your initial state object

  • options: StoreOptions & { provider?: boolean }

    • provider: If true, returns { store, useStore, Provider }
    • If false or omitted, returns a single hook plus .store

createCoreStore (Advanced)

function createCoreStore<T extends State>(
  initialState: T,
  options?: StoreOptions
): Store<T>;

Description:

Low-level store creation, without any React hooks. Perfect if you need to do SSR manually, or integrate with non-React frameworks. Usually you won’t call this directly unless you have a very custom setup.

Examples

Simple counter

import React from 'react';
import { createNeutrixStore } from 'neutrix';

interface CounterState {
  count: number;
}
const { useStore, store } = createNeutrixStore<CounterState>({ count: 0 });

function Counter() {
  const count = useStore(s => s.count);

  function increment() {
    store.set('count', count + 1);
  }

  return (
    <div>
      <p>Count is {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

Using middleware:

useCounterStore.store.use({
  onSet: (path, value, prevValue) => {
    console.log(`Changing ${path}: ${prevValue} -> ${value}`);
    return value;
  },
  onGet: (path, value) => {
    console.log(`Reading ${path}: ${value}`);
    return value;
  }
});

Async actions:

function doSomeAsyncStuff(state: CounterState) {
  return new Promise<Partial<CounterState>>(resolve => {
    setTimeout(() => {
      resolve({ count: state.count + 10 });
    }, 1000);
  });
}

const { useStore, store } = createNeutrixStore({
  count: 0,
  doSomeAsyncStuff // <= function in initial state
});

// in a component:
function BigIncrement() {
  const { doSomeAsyncStuff } = store.getState();

  async function handleClick() {
    await doSomeAsyncStuff();
  }

  return <button onClick={handleClick}>Increment by 10</button>;
}

Advanced Topics

Automatic dependency tracking

Neutrix tracks which state paths you read in a computed or subscription. Only those paths trigger re-renders. No manual memo or “array of dependencies” is needed.

Dev-tools integration

If you pass devTools: true in your options, Neutrix will connect to Redux DevTools automatically:

const { useStore } = createNeutrixStore(
  { count: 0 },
  { devTools: true, name: 'CounterStore' }
);

SSR Usage

To create SSR-friendly stores, you can use createCoreStore or a specialized SSR approach. For example:

export function createStoreForSSR(initialState) {
  const store = createCoreStore(initialState);
  return {
    store,
    getServerSnapshot: () => JSON.stringify(store.getState()),
    rehydrate: (snapshot: string) => {
      const parsed = JSON.parse(snapshot);
      Object.keys(parsed).forEach(key => {
        store.set(key, parsed[key]);
      });
    }
  };
}

Performance Optimizations

  • LRU Caching for computed values.
  • Proxy-based dependency tracking—only re-renders where needed.
  • Batching: You can batch multiple set() calls to avoid extra re-renders.

FAQ

  • Q: Do I need a Provider for everything?

  • A: No! If you prefer something like Zustand, just use createNeutrixStore without { provider: true }. If you need multiple stores or SSR, pass { provider: true }.

  • Q: How do I handle side effects or async logic?

  • A: You can define an async function in your store’s initial state or call store.action(fn). Both let you do async updates seamlessly.

  • Q: Does Neutrix replace Redux entirely?

  • A: Not necessarily. Neutrix can handle most use cases with less boilerplate, but Redux might still be used if you’re heavily invested in its ecosystem.

Contributing

We love contributions! Please see our contributing guide for details.

License

MIT


Built with ❤️ by oha