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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@ryact-utils/has-changes

v0.0.5

Published

A lightweight React hook utility that helps eliminate unnecessary `useEffect` usage by providing a simple way to detect value changes. Instead of setting up effects to track value changes, this hook gives you direct access to change detection and previous

Readme

@ryact-utils/has-changes

A lightweight React hook utility that helps eliminate unnecessary useEffect usage by providing a simple way to detect value changes. Instead of setting up effects to track value changes, this hook gives you direct access to change detection and previous values.

After reading You Might Not Need an Effect, I realized that I was using useEffect to detect changes in values. This is unnecessary and can be replaced with a simpler hook.

const [value, setValue] = useState(0);

// 😫 Common but unnecessary useEffect pattern
useEffect(() => {
	console.log(value); // side effect
}, [value]);

The Problem

One of the suggested solutions is to add another useState to track the previous value. This got tedious and I wanted to find a better solution.

// have to manage 2 state variables
const [value, setValue] = useState<T>({ ... });
const [prevValue, setPrevValue] = useState<T>({ ... });

const getHasChanged = (a: T, b: T) => {
  // implement comparison logic
	// (easy for primitives, but harder for objects and arrays)
}

if (getHasChanged(value, prevValue)) {
	console.log(value); // side effect
}

The Solution?

This custom hook is a more direct and cleaner approach to change detection, reducing the need for effect-based solutions.

const [value, setValue] = useState(0);

// 🎉 Clean and direct with useHasChanged
const [hasChanged, prevValue] = useHasChanged(value);
if (hasChanged) {
	console.log(value); // side effect
}

This hook also provides a better DX for handleing side effects, since there is no need to manage a dependency array.

It is also ideal for preventing unnecessary re-renders, since side effects are triggered during the render phase and updates are batched together with the initial change.

Installation

npm install @ryact-utils/has-changes
# or
yarn add @ryact-utils/has-changes
# or
pnpm add @ryact-utils/has-changes

Usage

When passing a primitive value or a function, the hook will use Object.is to compare the values.

import { useHasChanged } from '@ryact-utils/has-changes';

function MyComponent() {
	const [count, setCount] = useState(0);

	const [hasChanged, previousCount] = useHasChanged(count);

	// No useEffect needed! Just react to changes directly
	if (hasChanged) {
		console.log(count); // side effect
	}
	...
}

When passing an array, the hook will shallowly compare it's elements.

const [value1, setValue1] = useState(0);
const [value2, setValue2] = useState('Hello');

// hasChange will be false unless value1 or value2 changes
// [prevValue1, prevValue2]: [number, string]
const [hasChanged, [prevValue1, prevValue2]] = useHasChanged([value1, value2] as const);
if (hasChanged) {
	console.log('Values changed');
}

This works too for objects

const [value1, setValue1] = useState(0);
const [value2, setValue2] = useState('Hello');

// hasChange will be false unless value1 or value2 changes
// prevValue: { x: number, y: string }
const [hasChanged, prevValue] = useHasChanged({ x: value1, y: value2 });
if (hasChanged) {
	console.log('Values changed', prevValue.x, prevValue.y);
}

API

useHasChanged<T, TRunOnMount extends boolean = false>

A hook that tracks changes in a value and provides both the change status and the previous value.

Parameters

  • current: T - The current value to track
  • config?: HasChangeConfig<T, TRunOnMount> - Optional configuration object

Config Options

type HasChangeConfig<T, TRunOnMount extends boolean = false> = {
	comparison?: 'shallowish' | 'shallow' | ((a: T, b: T) => boolean);
	runOnMount?: TRunOnMount;
};
  • comparison (optional):

    • 'shallowish' (default) - Performs a 1 level deep comparison of arrays and objects, and uses Object.is for primitives
    • 'shallow' - Uses Object.is for strict equality comparison
    • Function - Custom comparison function that takes two arguments and returns a boolean
  • runOnMount (optional):

    • false (default) - Previous value will be initialized with current value
    • true - Previous value will be null and will immediately indicate that the value has changed on mount

Return Value

Returns a tuple [boolean, T | null]:

  1. boolean - Whether the value has changed
  2. T | null - The previous value (null if runOnMount is true and it's the first render)

Examples

Custom Comparison

const [value, setValue] = useState({ count: 0 });
const [hasChanged, prevValue] = useHasChanged(value, {
	comparison: (a, b) => a.count === b.count,
});

Run on Mount

const [value, setValue] = useState({ count: 0 });
const [hasChanged, prevValue] = useHasChanged(value, {
	runOnMount: true,
});
// prevValue will be null on first render and hasChanged will be true

Notes

  • This hook is not a replacement for useEffect. It is a replacement for the need to use useEffect to trigger side effects as a result of a value changing.
  • Because side effects are triggered during the render phase, it is important to ensure that you are following the rules of hooks.
const [value, setValue] = useState(0);

const ref = useRef('hi');

const [hasChanged] = useHasChanged(value);
if (hasChanged) {
	ref.current = 'Some other value'; // This will cause a warning
}

License

MIT