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

use-first-interaction

v0.1.0

Published

A React hook that reports the first time a visitor touches the page, for work that should not run before then

Readme

use-first-interaction

A React hook that reports the first time a visitor touches the page.

import { useFirstInteraction } from "use-first-interaction";

export default function Analytics() {
  const interacted = useFirstInteraction({ delay: 2500 });

  return interacted ? <Tag /> : null;
}

Analytics, chat widgets and heatmap recorders do not need to be in the first paint. They need to be there by the time someone is actually using the page.

https://use-first-interaction.kkweb.io loads a real chunk this way, and shows what it cost.

Install

npm install use-first-interaction

React 18 or newer. No dependencies.

useFirstInteraction(options?)

false until the first keydown, pointerdown, scroll, touchstart or wheel on the window, then true for good. It is false on the server and on the first client render, so it never changes what hydration compares.

| Option | Default | | | ---- | ---- | ---- | | delay | 0 | wait this many milliseconds after the interaction before reporting it | | events | the five above | what to listen for | | timeout | — | report the interaction anyway after this long | | disabled | false | report nothing at all |

delay. The interaction itself is the frame the visitor cares most about, and the one moment not to spend on a third party. Two or three seconds after it is usually invisible to them and early enough for the tag.

events. scroll is on the list because a reader who only reads never presses anything. pointerdown covers mouse, pen and touch; touchstart is kept for browsers without pointer events.

timeout. Off by default. A visitor who leaves without touching anything is exactly the one you did not want to load for — but if the tag has to fire on every visit, set it and be honest about the trade.

disabled. For switching the deferred work off in development, where a recorder session per hot reload helps no one.

useOnFirstInteraction(callback, options?)

Runs the callback once, and returns the same boolean.

useOnFirstInteraction(async () => {
  const { hotjar } = await import("react-hotjar");

  hotjar.initialize({ id, sv });
}, { delay: 2500 });

The callback is read through a ref, so passing a new function on every render does not run it again. If it throws or rejects, the reason goes to the console rather than becoming an unhandled rejection in your application.

Listeners

They are registered on window and passive — a non-passive scroll listener holds up the scroll it is watching — and they are removed the moment one of them fires.

What this is not

Not a consent gate. Deferring a tracker changes when it loads, not whether you were allowed to load it.

Not lazy rendering. For something that should appear when it scrolls into view, an intersection observer is the right instrument.

Licence

MIT