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

airyhooks

v0.4.0

Published

CLI to add React hooks to your project

Readme

airyhooks

airyhooks logo

Add production-ready zero-dependency React hooks without package installation directly to your project.

airyhooks is a CLI tool that lets you add tested, TypeScript-first React hooks directly to your codebase. Instead of installing a package, you copy exactly what you need. This gives you complete control: modify hooks for your use case, avoid dependency bloat, and eliminate version conflicts.

pnpm dlx airyhooks

Quick Start

1. Initialize

Create the configuration file and set your hooks directory:

pnpm dlx airyhooks init

This creates airyhooks.json in your project root and prompts you for the hooks directory path (default: src/hooks).

[!NOTE]
You can use airyhooks without initialization. It will use the default configuration.

2. Start Adding Hooks

Pick from all available hooks and add them to your project:

pnpm dlx airyhooks

Or add a specific hook directly:

pnpm dlx airyhooks add useDebounce

This creates the following structure:

hooks/
├── useDebounce/
│   ├── useDebounce.ts
│   └── index.ts

3. Use in Your Code

Import and use the hook in your components:

import { useDebounce } from "./hooks/useDebounce";

export function SearchComponent() {
  const [value, setValue] = useState("");
  const debouncedValue = useDebounce(value, 500);

  return (
    <div>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <p>Searching for: {debouncedValue}</p>
    </div>
  );
}

Available Hooks

| Hook | Description | | ------------------------- | ------------------------------------------------------- | | useClickAway | Detect clicks outside of an element | | useCopyToClipboard | Copy text to clipboard with status feedback | | useCounter | Manage numeric state with increment/decrement/reset/set | | useDebounce | Debounce a value with a specified delay | | useDebouncedCallback | Debounce a callback function with cancel support | | useDocumentTitle | Dynamically update the document title | | useEventListener | Attach event listeners with automatic cleanup | | useFetch | Fetch data with loading, error, and refetch support | | useHover | Track mouse hover state on elements | | useIntersectionObserver | Track element visibility in viewport | | useInterval | Call a callback at specified intervals | | useIsClient | Check if code is running on client (SSR-safe) | | useKeyPress | Detect specific keyboard key presses | | useLocalStorage | Sync state with localStorage | | useLockBodyScroll | Prevent body scrolling (useful for modals) | | useMeasure | Measure element dimensions with ResizeObserver | | useMedia | React to CSS media query changes | | useMount | Call a callback on component mount | | usePrevious | Track previous state or props value | | useScroll | Track scroll position of element or window | | useSessionStorage | Sync state with sessionStorage | | useThrottle | Throttle a value to update at most once per interval | | useThrottledCallback | Throttle a callback with leading/trailing options | | useTimeout | Call a callback after a timeout | | useToggle | Toggle a boolean value with convenient handlers | | useUnmount | Call a callback on component unmount | | useWindowSize | Track window dimensions |

Commands

airyhooks init

Initialize airyhooks in your project. Creates airyhooks.json configuration file and prompts for your hooks directory path.

pnpm dlx airyhooks init
# or
npx airyhooks init

airyhooks

Run the interactive hook picker to browse and add hooks to your project.

pnpm dlx airyhooks
# or
npx airyhooks

airyhooks add <hook-name>

Add a specific hook to your project. Creates the hook directory with TypeScript files.

pnpm dlx airyhooks add useDebounce

Options

  • --raw - Output only the raw hook template to console (no files created)

[!TIP] Use --raw to pipe the hook directly to a file or integrate with other tools:

pnpm dlx airyhooks add useDebounce --raw > useDebounce.ts

airyhooks list

List all available hooks with descriptions.

pnpm dlx airyhooks list

Usage Example

Here's a complete example using useDebounce for a search input:

import { useState } from "react";
import { useDebounce } from "./hooks/useDebounce";

export function App() {
  const [input, setInput] = useState("");
  const debouncedInput = useDebounce(input, 1000);

  return (
    <div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="Type something..."
      />
      <p>You typed: {debouncedInput}</p>
    </div>
  );
}

Configuration

The airyhooks.json file stores your configuration:

{
  "casing": "camelCase",
  "hooksPath": "src/hooks",
  "importExtension": "none",
  "includeTests": false,
  "structure": "nested"
}

Options

| Option | Type | Default | Description | | ----------------- | ------------------------------- | ------------- | ----------------------------------------------------------------- | | hooksPath | string | "src/hooks" | Directory path where hooks are added | | casing | "camelCase" | "kebab-case" | "camelCase" | Naming convention for hook directories and files | | structure | "flat" | "nested" | "flat" | Directory structure for hooks | | importExtension | "none" | "js" | "ts" | "none" | File extension used in barrel export imports | | includeTests | boolean | false | Include test files next to hook implementations when adding hooks |

hooksPath

The directory where hooks will be created. Can be any valid path relative to your project root.

{ "hooksPath": "src/lib/hooks" }

casing

Controls the naming convention for hook directories and files:

  • "camelCase" — Keeps the original hook name (e.g., useDebounce/useDebounce.ts)
  • "kebab-case" — Converts to kebab-case (e.g., use-debounce/use-debounce.ts)
# camelCase (default)
hooks/useDebounce/
├── useDebounce.ts
└── index.ts

# kebab-case
hooks/use-debounce/
├── use-debounce.ts
└── index.ts

[!TIP] You can override casing per-command with the --kebab flag: airyhooks add useDebounce --kebab

structure

Controls the directory structure for added hooks:

  • "flat" — All hooks are added directly under the hooksPath directory.
  • "nested" — Each hook gets its own subdirectory under hooksPath.
# flat (default)
hooks/
├── useDebounce.ts
└── useToggle.ts

# nested
hooks/
├── useDebounce/
│   ├── useDebounce.ts
│   └── index.ts
└── useToggle/
    ├── useToggle.ts
    └── index.ts

importExtension

Controls the file extension in the generated index.ts barrel export. Only relevant when using the structure: nested.

Choose based on your TypeScript configuration:

| Value | Output | When to use | | -------- | ------------------------------------------------ | --------------------------------------------------- | | "none" | export { useDebounce } from "./useDebounce" | moduleResolution: "bundler" (Vite, webpack, etc.) | | "js" | export { useDebounce } from "./useDebounce.js" | moduleResolution: "nodenext" or "node16" | | "ts" | export { useDebounce } from "./useDebounce.ts" | allowImportingTsExtensions: true in tsconfig |

includeTests

When true, airyhooks will also copy the test file for the hook next to the generated hook implementation. The behavior follows the configured structure:

  • With "nested", the test file is written inside the hook's subdirectory (e.g. useDebounce/useDebounce.test.ts).
  • With "flat", the test file is written directly under the hooksPath next to the hook file (e.g. useDebounce.test.ts).

You can enable this by setting it in airyhooks.json:

{ "includeTests": true }

You can also override this per-command with the CLI flag: airyhooks add <hook-name> --include-tests.

Package Managers

airyhooks works with all major package managers:

# pnpm
pnpm dlx airyhooks add useDebounce

# npm
npx airyhooks add useDebounce

# yarn
yarn dlx airyhooks add useDebounce

# bun
bunx airyhooks add useDebounce

Ready to use? Run pnpm dlx airyhooks init to get started!