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

@normalized-cache/react

v0.0.5

Published

React hooks (useModel, useModelAll) for @normalized-cache/core with selector-based rerender control.

Downloads

272

Readme

@normalized-cache/react

React hooks for @normalized-cache/coreuseModel and useModelAll, with selector-based rerender control.

Install

npm install @normalized-cache/core @normalized-cache/react

react (>=18) is a peer dependency and must be installed separately in your app.

Usage

Bind hooks to a specific cache instance once, in your own setup file:

// models/hooks.ts
import { createCacheHooks } from "@normalized-cache/react";
import { cache } from "./cache"; // your NormalizedCache instance

export const { useModel, useModelAll } = createCacheHooks(cache);

Then use them in components:

import { useModel, useModelAll } from "./models/hooks";

function UserName({ userId }: { userId: string }) {
  const userName = useModel("User", userId, (user) => user.name);
  return <span>{userName}</span>;
}

function UserList() {
  const names = useModelAll("User", (users) => users.map((u) => u.name));
  return <ul>{names.map((n) => <li key={n}>{n}</li>)}</ul>;
}

Selectors are type-safe (derived from the models registered on your cache) and let components re-render only when the selected value actually changes.

Selector stability

useModel and useModelAll are built on React's useSyncExternalStore, which requires getSnapshot() to return a value that is referentially stable (Object.is-equal) across calls when nothing has changed. If it doesn't, React re-renders, calls getSnapshot again, sees another new reference, and loops forever — this surfaces as Maximum update depth exceeded or "The result of getSnapshot should be cached to avoid an infinite loop."

cache.get()/cache.getAll() already return stable references for unchanged data, but a selector that derives a new array or object on every call breaks that guarantee even though the underlying data hasn't changed, e.g.:

// Allocates a new array every render, even when `posts` is unchanged.
const titles = useModel("User", userId, (user) => user.posts.map((p) => p.title));

Both hooks guard against this automatically: a selector's return value is compared to its previous return value with a one-level-deep shallow equality check (array elements, or object values, compared with Object.is) before deciding whether the component needs to re-render. If the new array/object is shallow-equal to the last one, the previously cached reference is reused, so getSnapshot stays stable and no loop occurs. Selectors that already return a stable reference (the entity itself, a primitive, a memoized value) are unaffected — an Object.is check still short-circuits first.

This only compares one level deep, matching the behavior of shallowEqual in Redux's react-redux and Zustand's useShallow. A selector returning nested arrays/objects (e.g. { posts: user.posts.map(...) } where posts is itself a new array each time) will not be considered equal — structure your selector to return a flat array/object, or compose multiple useModel/useModelAll calls, if you need to derive nested shapes.

License

MIT