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

netwrap

v4.0.0

Published

A package wrapper for helping teams around the world deliver consistent results when building react applications that connect to backend services. This package inspiration came from working in teams where frontend users could not achieve consistent result

Readme

netwrap

Lightweight request helpers with a React hook and a plain fetcher.

Installation

yarn add netwrap

Development commands

From the repo root:

# install workspace deps
yarn install

# build the netwrap package (ESM + CJS)
yarn workspace netwrap build

# run netwrap tests
yarn workspace netwrap test

# deploy netwrap (builds then publishes)
yarn workspace netwrap deploy

# run the Next.js web app
yarn workspace web dev

# build and start the Next.js web app
yarn workspace web build
yarn workspace web start

# run the API demo script
node apps/api/index.js

Peer Dependencies

  • react (only required when using useFetcher)

Usage

Entry points

  • netwrap: server-safe utilities and fetcher
  • netwrap/client: React hooks (client components only)

Warnings and deprecations

  • useFetcher must be imported from netwrap/client and used only in client components.
  • Client-component usage of fetcher is deprecated and will be removed in the next version. Use useFetcher instead.
  • fetcher is not reactive; do not destructure data or error if you expect live updates.
  • fetcher cache is instance-local. Recreate the instance and the cache resets.

useFetcher (React client component)

"use client";

import { useFetcher } from "netwrap/client";
import { useState } from "react";

export default function ClientPage() {
  const [log, setLog] = useState<string[]>([]);
  const { trigger, data, error, isLoading, invalidateCache } = useFetcher<
    void,
    { id: number; title: string }
  >({
    queryFn: async () => {
      const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
      return res.json();
    },
    onStartQuery: () => setLog((prev) => ["start", ...prev]),
    onSuccess: () => setLog((prev) => ["success", ...prev]),
    onError: () => setLog((prev) => ["error", ...prev]),
    onFinal: () => setLog((prev) => ["final", ...prev]),
  });

  return (
    <section>
      <h1>Client useFetcher test</h1>
      <p>Click to trigger a client-side fetch and watch state updates.</p>

      <div className="controls">
        <button onClick={() => trigger()} disabled={isLoading}>
          {isLoading ? "Loading..." : "Fetch data"}
        </button>
        <button onClick={() => invalidateCache()} disabled={isLoading}>
          Clear cache
        </button>
      </div>

      <div className="status">
        <div>Status: {isLoading ? "loading" : "idle"}</div>
        {error ? <div className="error">Error</div> : null}
        {data ? (
          <div>
            Result: #{data.id} - {data.title}
          </div>
        ) : (
          <div>No data yet</div>
        )}
      </div>

      <div className="events">
        <strong>Event log</strong>
        <ul>
          {log.slice(0, 6).map((item, idx) => (
            <li key={`${item}-${idx}`}>{item}</li>
          ))}
        </ul>
      </div>
    </section>
  );
}

fetcher (client component, deprecated)

"use client";

import { fetcher } from "netwrap";
import { useState } from "react";

export default function ClientFetcherPage() {
  const [log, setLog] = useState<string[]>([]);
  const [api] = useState(() =>
    fetcher<void, { id: number; title: string }>({
      queryFn: async () => {
        const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
        return res.json();
      },
      onStartQuery: () => setLog((prev) => ["start", ...prev]),
      onSuccess: () => setLog((prev) => ["success", ...prev]),
      onError: () => setLog((prev) => ["error", ...prev]),
      onFinal: () => setLog((prev) => ["final", ...prev]),
    }),
  );

  return (
    <section>
      <h1>Client fetcher test</h1>
      <p>Using fetcher in a client component with a stable instance.</p>

      <div className="controls">
        <button onClick={() => api.trigger()} disabled={api.isLoading}>
          {api.isLoading ? "Loading..." : "Fetch data"}
        </button>
        <button onClick={() => api.invalidateCache()} disabled={api.isLoading}>
          Clear cache
        </button>
      </div>

      <div className="status">
        <div>Status: {api.isLoading ? "loading" : "idle"}</div>
        {api.error ? <div className="error">Error</div> : null}
        {api.data ? (
          <div>
            Result: #{api.data.id} - {api.data.title}
          </div>
        ) : (
          <div>No data yet</div>
        )}
      </div>

      <div className="events">
        <strong>Event log</strong>
        <ul>
          {log.slice(0, 6).map((item, idx) => (
            <li key={`${item}-${idx}`}>{item}</li>
          ))}
        </ul>
      </div>
    </section>
  );
}

API

fetcher(options)

Returns:

  • trigger(triggerData?): executes the request
  • data: last response data (if successful)
  • error: last error (if any)
  • isLoading: loading state
  • getData(): get the latest data without subscribing
  • getError(): get the latest error without subscribing
  • getIsLoading(): get the latest loading state without subscribing
  • onLoadingChange(listener): subscribe to loading state changes
  • onDataChange(listener): subscribe to data changes
  • onErrorChange(listener): subscribe to error changes
  • invalidateCache(): clears cached response data

Note: fetcher is not reactive. Avoid destructuring data or error from it if you expect live updates; access them from the returned object (api.data) or use the change listeners.

useFetcher(options)

Returns:

  • trigger(triggerData?): executes the request
  • data: last response data (if successful)
  • error: last error (if any)
  • isLoading: loading state
  • invalidateCache(): clears cached response data

Options

Both fetcher and useFetcher accept:

  • queryFn (required): async function that performs the request
  • onStartQuery: called before the request begins; return a value to override trigger data (sync or async)
  • onSuccess: called with the response data
  • onError: called with the error
  • onFinal: called when the request completes (success or error)

Examples

Request with parameters

const { trigger } = fetcher({
  queryFn: async (id: number) => {
    const res = await fetch(`/api/items/${id}`);
    return res.json();
  },
});

await trigger(123);

Cache behavior (fetcher)

const { trigger, invalidateCache } = fetcher({
  queryFn: async () => {
    const res = await fetch("/api/value");
    return res.json();
  },
});

await trigger(); // fetches
await trigger(); // returns cached response
invalidateCache();
await trigger(); // fetches again

Cache behavior (useFetcher)

const { trigger, invalidateCache } = useFetcher({
  queryFn: async () => {
    const res = await fetch("/api/value");
    return res.json();
  },
});

await trigger(); // fetches
await trigger(); // returns cached response
invalidateCache();
await trigger(); // fetches again

Contributing

  1. Fork and clone the repo.
  2. Install dependencies with yarn install.
  3. Run tests with yarn workspace netwrap test.
  4. Open a PR with a clear description of the change.

License

MIT