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

@peerfold/react

v0.2.0

Published

React provider + hooks for the Peerfold API, built on @peerfold/api-client. Plain React, no data-fetching runtime dependency; SSR-safe.

Readme

@peerfold/react

React provider + hooks over @peerfold/api-client. Built with plain React — no data-fetching runtime dependency — and SSR-safe (no window at module scope; fetching runs in effects, so a server render just returns the initial loading state).

Deviation from the PRD (§5.3): the PRD sketched TanStack Query "under the hood". We keep the core zero-dependency instead — the hooks are a thin, auditable useEffect + AbortController layer. Drop TanStack Query in at the app level if you want its cache; these hooks don't require it.

Install

npm install @peerfold/react @peerfold/api-client

react (18 or 19) is a peer dependency; @peerfold/api-client is what you build the client with.

A note on naming (0.2.0). The component and class names are PeerfoldProvider / PeerfoldClient / PeerfoldError, the hook is usePeerfoldClient, the wire headers are Peerfold-Version / X-Peerfold-* and the browser storage keys are peerfold.*. 0.1.x shipped the old HubLMS-prefixed names — this is a breaking rename, so pin ^0.2.0.

Setup

Build a client (with a learner token from your auth flow) and pass it to the provider. The provider constructs nothing itself, so it is safe to render on the server.

import { PeerfoldClient } from "@peerfold/api-client";
import { PeerfoldProvider } from "@peerfold/react";

const client = new PeerfoldClient({
  baseUrl: "https://acme.site.hublms.com",
  learnerToken: () => tokenStore.get(), // provider fn → always reads the current token
});

export function App() {
  return (
    <PeerfoldProvider client={client}>
      <Catalog />
    </PeerfoldProvider>
  );
}

Read hooks

Each returns { data, error, loading, reload }.

import { useMe, useCatalog, useCourse, useEnrollments, useCertificates } from "@peerfold/react";

function Catalog() {
  const { data: courses, loading, error, reload } = useCatalog();
  if (loading) return <Spinner />;
  if (error) return <Error msg={error.message} onRetry={reload} />;
  return <ul>{courses!.map((c) => <li key={c.id}>{c.title}</li>)}</ul>;
}

function CoursePage({ slug }: { slug: string }) {
  const { data: course } = useCourse(slug);   // fetch is skipped while slug is falsy
  const { data: me } = useMe();
  // …
}

useCatalog, useEnrollments, useCertificates eagerly collect all pages via the client's iterate(); use client.*.iterate() directly for manual paging.

Optimistic progress

useProgressMutation is an optimistic, coalescing, retrying queue for progress events — call record freely from the player:

import { useProgressMutation } from "@peerfold/react";

function Lesson({ enrollmentId, lessonId }: { enrollmentId: string; lessonId: string }) {
  const { record, pending, error } = useProgressMutation({
    onSuccess: (result) => setProgress(result.percent_complete ?? 0),
  });

  return (
    <button
      onClick={() =>
        record(enrollmentId, { event_id: crypto.randomUUID(), type: "lesson_completed", lesson_id: lessonId })
      }
    >
      Mark complete {pending > 0 ? `(syncing ${pending}…)` : ""}
    </button>
  );
}

What it does:

  • Coalesces redundant events — repeated records for the same (enrollment, lesson, type) collapse to the latest one still queued.
  • Stable idempotency key per item — retries replay the server's stored response instead of double-appending.
  • Retries 429/5xx and transient network errors with exponential backoff, then surfaces the failure via onError.
  • Optimisticrecord returns immediately; your UI advances without awaiting the network.