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

nirdjs

v0.1.40

Published

An atomic state management library for React

Readme


Why nirdjs?

| | nirdjs | Redux | Zustand | Jotai | |---|---|---|---|---| | Bundle size | ~2 KB | ~7 KB | ~3 KB | ~8 KB | | Boilerplate | None | Heavy | Light | Light | | SSR isolation | Built-in | Manual | Manual | Provider | | Derived state | derive() | Selectors | Middleware | Derived atoms | | Batching | Deduplicated | Middleware | — | — | | TypeScript | First-class | Verbose | Good | Good |

nirdjs gives you atoms that live outside React. No providers, no context, no reducers. Just declare → use → done.


Install

npm install nirdjs
# or
bun add nirdjs
# or
pnpm add nirdjs

Quick Start

// counterAtom.ts — define once, import anywhere
import { atom, useValue } from 'nirdjs';

const counterAtom = atom(0);

export const useCounter = () => useValue(counterAtom);
export const inc = () => counterAtom.update(prev => prev + 1);
export const dec = () => counterAtom.update(prev => prev - 1);
export const reset = () => counterAtom.reset();
// Counter.tsx
import { useCounter, inc, dec, reset } from './counterAtom';

export const Counter = () => {
  const count = useCounter();

  return (
    <div>
      <span>{count}</span>
      <button onClick={dec}>−</button>
      <button onClick={inc}>+</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

That's it. useCounter() subscribes the component. inc, dec, and reset are plain functions — call them from anywhere: event handlers, effects, other modules, even outside React.


Core API

atom(initialValue, config?)

Creates a reactive atom. This is the fundamental building block.

import { atom } from 'nirdjs';

const nameAtom = atom('Alice');

nameAtom.get();          // 'Alice'
nameAtom.set('Bob');     // set directly
nameAtom.update(n => n.toUpperCase()); // update from previous value

| Method | Description | |---|---| | .get() | Returns the current value | | .set(value) | Sets a new value, notifies subscribers | | .update(fn) | Applies fn(currentValue) and sets the result | | .sub(fn) | Subscribe to changes — fn(nextValue, prevValue) | | .unsub(fn) | Unsubscribe | | .toString() | Debug-friendly string representation |

Config options

const atom = atom(value, {
  debugLabel: 'myAtom',        // label for debugging
  ignoreWhen: (prev, next) =>  // skip notification when true
    prev === next,             // (default: strict equality + NaN handling)
  allowFnValue: false,         // set to true to store functions as values
});

useValue(atom)

React hook that subscribes the component to an atom. Re-renders only when the atom's value changes.

import { atom, useValue } from 'nirdjs';

const themeAtom = atom<'light' | 'dark'>('dark');

const ThemeIndicator = () => {
  const theme = useValue(themeAtom);
  return <span>Current theme: {theme}</span>;
};

Derived State

derive(source, get, set, config?)

Creates an atom that derives its value from a source atom. Changes flow in both directions.

import { atom, derive, NeverSet } from 'nirdjs';

const userAtom = atom({ first: 'Ada', last: 'Lovelace' });

// Read-only derived atom
const fullNameAtom = derive(
  userAtom,
  user => `${user.first} ${user.last}`,
  NeverSet, // marks this atom as read-only
);

fullNameAtom.get(); // 'Ada Lovelace'
// Read-write derived atom
const celsiusAtom = atom(0);

const fahrenheitAtom = derive(
  celsiusAtom,
  c => c * 9 / 5 + 32,           // celsius → fahrenheit
  (f) => (f - 32) * 5 / 9,       // fahrenheit → celsius
);

fahrenheitAtom.set(212);
celsiusAtom.get(); // 100

propertyAtom(source, key, config?)

Shorthand for deriving a single property from an object atom. Read-write by default.

import { atom, propertyAtom } from 'nirdjs';

const settingsAtom = atom({ volume: 80, muted: false });

const volumeAtom = propertyAtom(settingsAtom, 'volume');
volumeAtom.set(50); // settingsAtom is now { volume: 50, muted: false }

Arrays

splitAtom(source, itemConfig?, containerConfig?)

Splits an array atom into an array of individual item atoms. Changing one item re-renders only that item's subscribers — not the entire list.

import { atom, splitAtom, useValue } from 'nirdjs';

const todosAtom = atom(['Buy milk', 'Write code', 'Ship it']);
const todoAtomsAtom = splitAtom(todosAtom);

const TodoList = () => {
  const todoAtoms = useValue(todoAtomsAtom);
  return (
    <ul>
      {todoAtoms.map((todoAtom, i) => (
        <TodoItem key={i} atom={todoAtom} />
      ))}
    </ul>
  );
};

const TodoItem = ({ atom }: { atom: Atom<string> }) => {
  const text = useValue(atom);
  return <li>{text}</li>;
};

updateElt(arrayAtom, index, updateFn)

Update a single element by index without replacing the whole array.

import { atom, updateElt } from 'nirdjs';

const scores = atom([10, 20, 30]);
updateElt(scores, 1, prev => prev + 5); // [10, 25, 30]

Batching

Wrap multiple atom updates in batch() to defer notifications until the end. Notifications are deduplicated per atom — if you set the same atom 10 times, subscribers fire only once with the final value.

import { atom, batch } from 'nirdjs';

const x = atom(0);
const y = atom(0);

await batch(() => {
  x.set(1);
  y.set(1);
  x.set(2);  // overwrites the first x.set
  y.set(2);  // overwrites the first y.set
});
// x subscribers called once with (2, 0)
// y subscribers called once with (2, 0)

Nested batches are supported — notifications flush when the outermost batch completes.


Helpers

import { atomSetter, atomGetter } from 'nirdjs';

const darkModeAtom = atom(false);

// Create standalone getter/setter functions
export const getDarkMode = atomGetter(darkModeAtom);  // () => boolean
export const setDarkMode = atomSetter(darkModeAtom);  // (value: boolean) => void

SSR

For server-side rendering, each request must have its own isolated store. nirdjs provides AsyncLocalStorage-based isolation out of the box.

import { atom, useValue } from 'nirdjs';
import {
  disableDefaultStore,
  setStoreProvider,
  createAtomStore,
} from 'nirdjs';
import {
  asyncLocalStorageStoreProvider,
  execWithAtom,
} from 'nirdjs/ssr/AsyncLocalStorageAtomProvider';

// 1. Disable the global default store
disableDefaultStore();
setStoreProvider(asyncLocalStorageStoreProvider);

// 2. Each request gets its own store
const pageAtom = atom('');

app.get('/', (req, res) => {
  const html = execWithAtom(createAtomStore(), () => {
    pageAtom.set(req.url);
    return renderToString(<App />);
  });
  res.send(html);
});

Two concurrent requests will never share state — each runs in its own AsyncLocalStorage context.


TypeScript

nirdjs is written in TypeScript. All atoms are fully typed — generics propagate through derive, propertyAtom, splitAtom, and useValue automatically.

// Type is inferred as Atom<{ name: string; age: number }>
const userAtom = atom({ name: 'Alice', age: 30 });

// Type is inferred as Atom<string>
const nameAtom = propertyAtom(userAtom, 'name');

// Explicit generic when needed
const idAtom = atom<string | null>(null);

API Reference

| Export | Description | |---|---| | atom(value, config?) | Create a reactive atom | | useValue(atom) | React hook — subscribe a component to an atom | | derive(source, get, set, config?) | Create a derived atom from a source atom | | NeverSet | Pass as the setter to derive() for read-only atoms | | propertyAtom(source, key, config?) | Derive a single property of an object atom | | splitAtom(source) | Split an array atom into individual item atoms | | updateElt(arrayAtom, i, fn) | Update one element in an array atom | | batch(fn) | Batch updates, deduplicate notifications | | atomSetter(atom) | Create a standalone setter function | | atomGetter(atom) | Create a standalone getter function | | createAtomStore() | Create an isolated store (for SSR) | | disableDefaultStore() | Disable the global store (for SSR) | | setStoreProvider(fn) | Set a custom store resolver | | isIdentical | Default equality check (=== + NaN) | | neverIgnore | Config value to always notify on set |


License

Apache-2.0 — Dima Kaigorodov

Links