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

@lazylab/react-use-stable-state

v1.0.0

Published

A hook that only updates state if the value has actually changed, with optional deep compare support.

Readme

@lazylab/react-use-stable-state

NPM Downloads NPM Version License: MIT

A React hook that only updates state if the value has actually changed, with optional custom comparison support (like deep or shallow equality).

The Problem

A classic issue in React is triggering unnecessary re-renders when you update state with an object or an array that is structurally identical, but has a new reference:

// This triggers a re-render even though the content is exactly the same!
setState({ page: 1 })

Or when you receive data from an API where the content is unchanged but the object reference is new. React compares state by reference (Object.is), not by structure.

The Solution

useStableState provides a minimal, predictable wrapper around useState that allows you to specify exactly how the old and new state should be compared.

🆚 useState vs useStableState

| Feature | useState | useStableState | |---------|-----------|------------------| | Comparison Method | Fixed to Object.is (Reference equality) | Pluggable (Default: shallowEqual, supports Deep/Reference) | | Object Updates | Re-renders if reference changes (even if identical) | Prevents re-render if content is equal | | API Responses | Triggers re-render on identical polled data | Safely ignores unchanged data | | Learning Curve | Standard React | Zero (Same API as useState) | | Bundle Size | Built-in | < 1kb (Zero dependencies) |

Installation

npm install @lazylab/react-use-stable-state
# or
yarn add @lazylab/react-use-stable-state
# or
pnpm add @lazylab/react-use-stable-state

Usage

Basic Usage (Default: shallowEqual)

By default, useStableState uses a built-in shallowEqual comparison. This means if you pass an object with the exact same top-level properties, it will not trigger a re-render:

import { useStableState } from '@lazylab/react-use-stable-state';

function Filters() {
  const [filters, setFilters] = useStableState({ page: 1, sort: 'asc' });

  const handleUpdate = () => {
    // This will NOT trigger a re-render because it is shallowly equal!
    setFilters({ page: 1, sort: 'asc' }); 
  };

  return <button onClick={handleUpdate}>Update Filters</button>;
}

With Reference Equality (Like standard useState)

If you want the exact same behavior as useState (comparing by reference), you can pass Object.is as the compare function:

import { useStableState } from '@lazylab/react-use-stable-state';

function App() {
  const [count, setCount] = useStableState(0, { compare: Object.is });
}

With Deep Comparison (e.g., fast-deep-equal or lodash.isEqual)

If you have deeply nested objects (like complex API responses), you can inject any comparison library you want:

import { useStableState } from '@lazylab/react-use-stable-state';
import deepEqual from 'fast-deep-equal';

function DataView({ initialData }) {
  const [data, setData] = useStableState(initialData, {
    compare: deepEqual
  });

  const handleNewData = (newData) => {
    // Only re-renders if the actual deep content changed
    setData(newData);
  };
}

API

useStableState(initialValue, options?)

Parameters

  • initialValue: The initial state value (or a lazy initializer function () => initialValue).
  • options (optional):
    • compare: A function (prev: T, next: T) => boolean that returns true if the values should be considered equal. Defaults to Object.is.

Returns

Returns a tuple [state, setState] exactly like standard useState. setState supports both direct values and functional updates (prev => next).

shallowEqual(a, b)

A microscopic utility function that performs a shallow comparison of two objects. It returns true if they have the same keys with the same top-level values (compared using Object.is).

Philosophy

We didn't want to:

  • Reinvent a state manager
  • Create magical proxies
  • Force heavy comparison libraries on you

We wanted:

  • A predictable, explicit wrapper
  • A hook that solves the origin of the problem (setState) rather than patching the symptoms (e.g. useDeepCompareEffect)

License

MIT