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-lane

v0.8.0

Published

Promise-first, transition-native data fetching for React 19 — a minimal data layer that keeps keyed promises in React state while React owns the UI.

Readme

use-lane

npm version license

Promise-first. Transition-native.

Lane is a minimal data layer for React 19. It keeps each keyed read's promise in React state, so Suspense reads it, transitions replace it, and the screen you're looking at stays live while the next data loads. No spinner flash, no torn UI, no parallel query state machine.

React 19 ships the primitives to render async data — use(promise) for data, Suspense for loading, Error Boundaries for errors, transitions for non-blocking updates, and useOptimistic / useActionState for mutations. It doesn't ship the small coordination layer underneath: stable promise identity by key, one shared request across components, and a way to replace that promise after the source changes. Lane is exactly that layer — and nothing React already provides.

const { promise } = useLane({
  key: ["user", id],
  loader: ({ signal }) => fetchUser(id, signal),
});
const { data: user } = use(promise); // Suspense owns loading, Error Boundaries own errors

Why Lane

Libraries like SWR and TanStack Query own a resolved-value cache plus their own loading/error/status objects, optimistic patches, and mutation helpers. Lane takes the opposite split: the promise is the state. Lane owns only the promise identity and lifecycle behind each key; React owns the UI state it was designed to own.

  • Promise-first by construction. Lane keeps each key's current promise in React state instead of reading resolved query state from an external store. use(promise) is the only read path: Suspense owns the first load, an Error Boundary owns the first failure, and a refresh swaps in the next promise.
  • Promise replacement is transition-native. Invalidation and revalidation replace the promise through a transition. Key changes compose with startTransition or useDeferredValue, so the same interruptible transition model drives the rest of your app and keeps the current screen live.
  • One mental model. Mutate the source, invalidate the read, render from the next promise — the same model you already use next to Server Components.
  • No parallel state machine. No isLoading / isError / status fields. use(promise) gives you { data } (and a refreshError only when a refresh fails over existing data); Suspense and Error Boundaries do the rest.
  • No mutation helper, by design. Mutations stay in React primitives, so optimistic UI lives next to the action that triggered it instead of in a global cache that needs rollback semantics.
  • Minimal on purpose. A typical LaneProvider + useLane import is about 3.4 kB minified and Brotli-compressed, and importing every export costs 4.7 kB — that is the whole ceiling. Lane stays small because it does not reimplement the UI state machine React already ships.

Requirements

React 19.2+ (Lane uses useEffectEvent). React is a peer dependency.

Lane leans on React core (use, Suspense, useTransition, useEffectEvent), never react-dom, so it runs in any React renderer — the browser, React Native, an Ink CLI, or your own. See Environments for CLI / React Native setup.

Install

npm install use-lane
# or: pnpm add use-lane
# or: yarn add use-lane

Quick start

1. Wrap your client tree in a LaneProvider.

"use client";

import { LaneProvider } from "use-lane";

export function Providers({ children }: { children: React.ReactNode }) {
  return <LaneProvider>{children}</LaneProvider>;
}

2. Read with useLane and unwrap with use. Lane returns the promise; a Suspense boundary owns the loading state and an Error Boundary owns the initial-load failure.

"use client";

import { Suspense, use } from "react";
import { useLane } from "use-lane";

function Profile({ userId }: { userId: string }) {
  const { promise } = useLane({
    key: ["user", userId],
    loader: async ({ signal }) => {
      const res = await fetch(`/api/users/${userId}`, { signal });
      if (!res.ok) throw new Error("Failed to load user");
      return (await res.json()) as User;
    },
  });

  const { data: user } = use(promise);
  return <h1>{user.name}</h1>;
}

export function UserProfile({ userId }: { userId: string }) {
  return (
    <Suspense fallback={<p>Loading…</p>}>
      <Profile userId={userId} />
    </Suspense>
  );
}

3. Converge after a mutation by invalidating the source. Mounted readers re-read through a transition; isInvalidationPending tells you it is happening.

"use client";

import { useLaneInstance } from "use-lane";

function RenameButton({ userId }: { userId: string }) {
  const lane = useLaneInstance();

  async function rename(name: string) {
    await fetch(`/api/users/${userId}`, {
      method: "PATCH",
      body: JSON.stringify({ name }),
    });
    // Source changed → re-read the affected key. React renders the next promise.
    lane.invalidate(["user", userId]);
  }

  return <button onClick={() => rename("Ada")}>Rename</button>;
}

Core concepts

  • Transition-native re-reads. Updates run through useTransition, so the previous UI stays mounted and interactive while the next promise resolves — isInvalidationPending and isBackgroundPending tell you which is in flight. Pair a key with useDeferredValue for search and filter UIs. (Initial loads with no prior data still suspend to a Suspense fallback.)
  • Keys are structural arrays (["task", id]). They are matched exactly, or by prefix / predicate for scoped operations. Date segments are supported.
  • Invalidation-driven re-reads. invalidate clears the cached promise and notifies mounted readers, which create the next promise from their current loader. Explicit (transition) and automatic (focus / mount / polling, reported as isBackgroundPending) re-reads are kept separate.
  • Stale-on-error. A failed refresh keeps serving the last fulfilled value; the promise resolves to { data, refreshError }, so use(promise) surfaces the stale data and the error together. Only an initial load (no previous value) rejects the promise and reaches the Error Boundary.
  • Authoritative publication. set / update publish server-confirmed data to exact keys the client owns; LaneHydration publishes RSC- or router-loaded data and overwrites authoritatively on navigation.
  • Ownership is decided per key, and enforced. A key the client fetches is client-owned. A key a publication seeds is not: read it with laneRead<T>({ key, loader: external }) — a loader that waits for the publication instead of fetching — and the client mutation surface (set / update / invalidate / remove) throws on it. Mutations go back through the owner (Server Action → revalidate → republish); immediacy is useOptimistic over the read value.
  • Lifecycle built in. Garbage collection (gcTime, default 5 min), retry / retryDelay, and refetchOnFocus / refetchOnMount / refetchOnReconnect revalidation. Polling is userland — a self-scheduled invalidate.
  • Optimistic UI stays local. Lane ships no mutation helper; use useOptimistic / useActionState in the component that owns the action.
  • A read can be one value. laneRead({ key, loader, ...options }) colocates a read the way react-query's queryOptions() does. The loaded type rides on the key it hands back (like react-query's DataTag), so set / update are type-checked from the key alone and a mutation path never imports a fetcher.
  • The session lives on the lane, not in the read. Declare it once (LaneRegister), supply it at the provider, and every loader is handed it as meta. A read's arguments stay exactly what decides its key, so .key is reachable from a mutation module or a Server Component — no parallel map of bare keys.

API at a glance

| Export | Purpose | | --- | --- | | LaneProvider | Provides a Lane instance to the tree; wires focus / reconnect revalidation via a pluggable eventSource (browser default; React Native / CLI / custom). | | useLane(read) | Read a key. Returns { promise, isInvalidationPending, isBackgroundPending, invalidate }; use(promise) yields { data, refreshError }. | | useLanePromise(read) | Thin wrapper returning just promise. | | laneRead({ key, loader, …options }) | Colocate a read's key, loader, and options in one value — react-query's queryOptions() for Lane. Reads take the whole thing (useLane, useLanesAll, prefetch); entry operations take its key. | | laneKey<T>(key) | A key that carries what its entry holds, so set / update through it are type-checked — no loader needed. | | LaneRegister | Declare what loaders are handed besides the key (a session, a tenant, an API client). Supplied as <LaneProvider loaderMeta={…}>, received as loader({ meta }). react-query's Register, with the value on the lane so it is mandatory. | | laneSnapshot(read, data) | One hydration entry, type-checked: T comes from the read's key, so a seed can't pair a key with the wrong data. | | useInfiniteLane(read) | A cursor-paginated list under one key. Returns { promise, loadMore, … }; use(promise) yields { pages, params, hasNext }. Colocate it with infiniteLaneRead. | | useLaneInstance() | The current Lane instance, for invalidate / set / update / remove from event handlers. | | createLane(options?) | Create a Lane instance manually (e.g. to share one across providers or seed on the server); accepts { gcTime }. | | LaneHydration | Publish a payload of snapshots as authoritative values — RSC props or a client router's loader data. | | external | The loader for a key its owner publishes: waits for the publication, never fetches. laneRead<T>({ key, loader: external }). |

Lane instance methods: invalidate / invalidateAll, set, update / updateAll, remove / removeAll — all keyed; set / update are checked when given a typed key. useLane options: staleTime, whenStale, retry, retryDelay, refetchOnFocus, refetchOnMount, refetchOnReconnect. createLane options: gcTime. Loaders receive { key, signal, current }, where current is the entry's last fulfilled value.

See the API reference for full signatures and semantics.

Documentation

Agent skill

This package ships an Agent Skills skill, so AI coding agents get use-lane-aware guidance that is version-locked to the installed package. It lives at skills/use-lane/SKILL.md and is self-contained — the full documentation is bundled alongside it as references.

If your project uses an AI agent, point it at the skill from your AGENTS.md / CLAUDE.md:

## Agent skills

Before editing React data-loading code, read the use-lane skill at
`node_modules/use-lane/skills/use-lane/SKILL.md` — use it for Suspense, `use()`,
transitions, invalidation, refetching, optimistic UI, or React Query / SWR
migration work.

License

MIT © Kento Moriwaki