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

@oratis/use-draft-autosave

v1.0.0

Published

Debounced localStorage draft-autosave hook for React. SSR-safe, zero dependencies, configurable storage backend and key prefix.

Readme

use-draft-autosave

npm version bundle size license types

Never lose a half-written form again. A tiny (~0.5 kB), dependency-free React hook that debounces the current form value into localStorage and gives you back what you saved — so a refresh, an accidental tab close, or a crash doesn't wipe the user's work.

  • 🪶 Zero dependencies, ~0.5 kB min+gzip
  • ⏱️ Debounced writes (configurable delay)
  • 🖥️ SSR-safe — no window access during render, exposes a hydrated flag
  • 🔌 Pluggable storagelocalStorage, sessionStorage, or your own
  • 🧩 Fully typed generic value, ships ESM + CJS + .d.ts

Install

npm install @oratis/use-draft-autosave
# or: pnpm add / yarn add / bun add

react >= 16.8 is a peer dependency.

Usage

The hook is a pure sink: you own the value, and it persists whatever you pass. Hydrate your initial state from loadDraft, then feed state back in.

import { useState } from "react";
import { useDraftAutosave, loadDraft, clearDraft } from "@oratis/use-draft-autosave";

function CommentBox() {
  // 1. Seed state from a previously saved draft (runs once).
  const [text, setText] = useState(() => loadDraft<string>("comment") ?? "");

  // 2. Autosave on every change, debounced.
  const { hydrated, lastSavedAt, clear } = useDraftAutosave("comment", text);

  async function submit() {
    await postComment(text);
    clear();      // drop the draft once it's safely submitted
    setText("");
  }

  return (
    <div>
      <textarea value={text} onChange={(e) => setText(e.target.value)} />
      {hydrated && lastSavedAt && (
        <small>Draft saved {new Date(lastSavedAt).toLocaleTimeString()}</small>
      )}
      <button onClick={submit}>Post</button>
    </div>
  );
}

Works with objects too — the value is JSON-serialized:

const [form, setForm] = useState(
  () => loadDraft<Profile>("profile") ?? { name: "", bio: "" }
);
useDraftAutosave("profile", form, { delay: 500 });

API

useDraftAutosave(key, value, options?)

Debounced autosave of value under ${prefix}${key}.

| Option | Type | Default | Description | | --- | --- | --- | --- | | delay | number | 1000 | Debounce delay in ms before a write. | | skipInitial | boolean | true | Don't re-save the value you just hydrated from a draft. | | prefix | string | "draft-" | Key prefix for every storage entry. | | storage | () => Storage \| null | window.localStorage | Storage backend resolver. Return null to no-op. |

Returns:

| Field | Type | Description | | --- | --- | --- | | hydrated | boolean | true once mounted on the client. Gate storage-derived UI on this to avoid hydration mismatches. | | lastSavedAt | number \| null | Epoch ms of the last successful write. | | clear() | () => void | Remove the draft and reset lastSavedAt. |

loadDraft<T>(key, options?)T | null

Read a saved draft. Returns null when missing, corrupt, or during SSR. Accepts prefix / storage in options.

clearDraft(key, options?)

Remove a saved draft. No-op when storage is unavailable.

Why a sink, not a source?

Hooks that both own and persist state tend to fight your component over who holds the truth (initial values, resets, controlled inputs). useDraftAutosave sidesteps that: you own the state, hydrate it from loadDraft once, and the hook only ever writes. Predictable, and it composes with any state library.

Next.js / SSR

Reading storage during render causes hydration mismatches. Seed with useState(() => loadDraft(...)) (the initializer runs on the client for client components) and gate any "saved at" UI behind the returned hydrated flag, as shown above.

License

MIT © oratis