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

keck

v2.2.0

Published

Simple observable state ✨🔭 for React and vanilla JS

Readme

Keck is observable state for React and plain TypeScript. Wrap an object, read the properties you render, then mutate the object directly. Keck re-renders only the components that read the properties that changed.

import { useObserver } from "keck/react";

function Counter() {
  const state = useObserver({ count: 0 });

  return (
    <button type="button" onClick={() => state.count++}>
      Count: {state.count}
    </button>
  );
}

No setters. No reducers. No action names. No selectors. Just normal objects with fine-grained subscriptions. The returned proxy has the same TypeScript type as the object you pass in — no wrapper types or casting required.

Why Keck

  • Direct mutation with React updates: write state.user.name = "Ada" and React updates the components that depend on that value.
  • Fine-grained rendering: reads during render become subscriptions, so unrelated changes do not fan out through the tree.
  • Shared state without providers: several components can observe the same object and each re-renders independently for the properties it actually read.
  • Derived values: compute values from state and re-render only when the computed result changes.
  • TypeScript-first: observable values keep the exact type of the object you pass in — no wrapper types, no type gymnastics.

If You're Already Using...

Valtio — Valtio requires two mental models: a mutable proxy for writing and an immutable snapshot for reading. Keck uses a single proxy for both. You read and write through the same object everywhere, and subscription tracking happens automatically from what you render — no useSnapshot call, no separate read/write paths to keep straight.

Zustand — Zustand gives you fine-grained control via selectors, but you have to write them. Keck derives subscriptions from your render output automatically, so components update only for the data they actually rendered without any manual selector work. There's also no store factory or set callback — just a plain object you mutate directly.

Jotai or Recoil — Atom-based libraries work well when state is naturally small and independent, but fitting relational or nested data into atoms often takes real effort. Keck lets you keep state as plain nested objects with no atom definitions, no selectors, and no Provider. If your state already looks like a JavaScript object, Keck fits it without restructuring.

Installation

npm install keck

3.44 KB minified and gzipped (core). The React entry point adds 757 B. No runtime dependencies — React is a peer dependency.

Requires React 18.2 or newer. Compatible with Strict Mode, Suspense, and startTransition.

A Real Shared Store

Keck works well when several components need the same data but not the same render schedule. Each component calls useObserver() with the same underlying object.

// store.ts
export const store = {
  user: {
    id: "user_123",
    name: "Ada Lovelace",
  },
  cart: {
    items: [] as Array<{ id: string; name: string; price: number }>,
  },
  ui: {
    cartOpen: false,
  },
};
// CartButton.tsx
import { useObserver } from "keck/react";
import { store } from "./store";

export function CartButton() {
  const state = useObserver(store);

  return (
    <button type="button" onClick={() => (state.ui.cartOpen = true)}>
      Cart ({state.cart.items.length})
    </button>
  );
}
// AddToCartButton.tsx
import { useObserver } from "keck/react";
import { store } from "./store";

export function AddToCartButton(props: {
  product: { id: string; name: string; price: number };
}) {
  const state = useObserver(store);

  return (
    <button type="button" onClick={() => state.cart.items.push(props.product)}>
      Add to cart
    </button>
  );
}

CartButton re-renders when cart.items.length changes. AddToCartButton does not re-render when the cart changes because it does not read cart data during render.

Derived Values

Use derive() when the UI depends on a computed result rather than every source value.

import { derive } from "keck";
import { useObserver } from "keck/react";
import { store } from "./store";

export function FreeShippingNotice() {
  const state = useObserver(store);

  const hasFreeShipping = derive(() => {
    return state.cart.items.reduce((sum, item) => sum + item.price, 0) >= 100;
  });

  return hasFreeShipping ? (
    <p>Free shipping unlocked.</p>
  ) : (
    <p>Spend $100 or more to unlock free shipping.</p>
  );
}

Derived values can also take a custom equality function, which is useful for arrays and objects.

import { derive, shallowCompare } from "keck";
import { useObserver } from "keck/react";
import { store } from "./store";

export function CartItemList() {
  const state = useObserver(store);

  // Re-render only when the set of IDs actually changes, not on every cart mutation.
  const cartItemIds = derive(
    () => state.cart.items.map((item) => item.id),
    shallowCompare,
  );

  return cartItemIds.map((id) => <CartItem key={id} id={id} />);
}

How It Works

useObserver(data) returns a proxy for data. When a component renders, Keck tracks the leaf reads — primitives, size, length, has(...), and any other value that is not itself an observable object. When you later write through any Keck proxy for the same underlying object, Keck notifies only the observers whose reads were affected.

function ProfileName() {
  const state = useObserver(store);

  return <h2>{state.user.name}</h2>;
}

This component re-renders when state.user.name changes. It does not re-render when cart items, UI flags, or other user fields change.

For effects that need to respond to any nested change under an object, use deep().

import { deep, unwrap } from "keck";
import { useEffect } from "react";

function AutosaveProfile() {
  const state = useObserver(store);

  useEffect(() => {
    void fetch("/api/profile", {
      method: "POST",
      body: JSON.stringify(unwrap(state.user)),
    });
  }, [deep(state.user)]);

  return null;
}

Documentation

  • Getting started: install, first component, shared state, and common patterns.
  • React guide: useObserver, callback overloads, dependencies, refs, and React gotchas.
  • Vanilla TypeScript guide: observe, focused observers, and shared module-level state outside React.
  • Custom classes: registering classes, methods, getters and setters, async writes.
  • Recipes: user accounts, shopping carts, persistence, API boundaries, and form resets.
  • Mental model: the rules that make Keck predictable.
  • API reference: all public exports.

API at a Glance

import { atomic, deep, derive, observe, ref, unwrap } from "keck";

import { reactRef, useObserver } from "keck/react";

| API | Use it for | | --- | --- | | useObserver(data, deps?) | React state that re-renders from properties read during render. | | useObserver(data, cb, deps?) | React state plus a synchronous callback on any change. | | useObserver(data, { derive, onChange, isEqual? }, deps?) | React state plus a synchronous callback when a derived result changes. | | observe(data, cb?) | Observable state outside React. | | observe(data, { derive, onChange, isEqual? }) | Observable state outside React with a derived callback. | | derive(fn, isEqual?) | Computed values that notify only when the result changes. | | deep(value) | Subscribe to any nested change under an observable object. | | unwrap(value) | Get the raw object at API and library boundaries. | | atomic(fn) | Batch multiple writes into one notification pass. |

Reliability

Keck is used in production applications. The test suite covers React rendering behavior, vanilla observers, derived values, arrays, Maps, Sets, custom classes, refs, and utilities.

Test Suites: 30 passed, 30 total
Tests:       159 passed, 159 total

License

MIT