Small, strict, modern React hooks — the ones you end up writing in every project, packaged the right way.
Status: bootstrapping — the packaging, CI, docs, and release pipeline are in place; the hook surface is still one hook and growing. As much a showcase of hook-packaging best practices as a hooks library.
pnpm add @goodnight-dev/react-hooksRequires React >=19.
// 1. Barrel — everything from one entry point.
import { useLocalStorage, useTheme } from '@goodnight-dev/react-hooks';
// 2. Subpath — smallest possible import, no barrel involved at all.
import { useTheme } from '@goodnight-dev/react-hooks/useTheme';
import { useLocalStorage } from '@goodnight-dev/react-hooks/useLocalStorage';Both are fully typed, ESM-only, and tree-shakable.
| Hook | Description |
|---|---|
useTheme() |
Reads the OS prefers-color-scheme, live-updating on change. |
useLocalStorage(key, initial) |
Reads and writes a JSON-serializable value in localStorage, syncing across tabs. |
import { useLocalStorage, useTheme } from '@goodnight-dev/react-hooks';
// Module scope: created once when this file loads, never re-allocated on
// render, so there's nothing to memoize.
const defaultSettings = { seen: false };
function App() {
const { theme, isDarkMode } = useTheme();
const [settings, setSettings] = useLocalStorage(
`users-${userId}`,
defaultSettings,
);
if (theme === undefined) return null; // avoid a flash of the wrong theme
return (
<div data-theme={theme}>
{isDarkMode ? '🌙' : '☀️'}
<button onClick={() => setSettings((s) => ({ ...s, seen: true }))}>
Dismiss
</button>
</div>
);
}See src/use-theme.md and
src/use-local-storage.md for the design
rationale behind each.
- 100% TypeScript with declaration files generated for consumers
- Modern ESM only, React
>=19peer dependency - SSR-safe and concurrent-safe: every hook is built on
useSyncExternalStorewhere it subscribes to something outside React - Strictest practical linting + formatting (typescript-eslint
strictTypeChecked,eslint-plugin-react-hooks, Prettier) - Importable as a whole or by tree-shakable subpath, per hook
- Comprehensive generated API docs
- Zero third-party runtime dependencies
This repo is opinionated about how the code is written. See CONTRIBUTING.md for the full rationale; in short:
- SSR-safe and concurrent-safe by default. External state (browser APIs,
storage, sockets) is read through
useSyncExternalStore, neveruseEffect+useState. A server render never guesses a client-only value. - Zero third-party runtime dependencies. Installing this package adds
nothing to your
node_modulesbut our own code — React itself is a peer dependency. - Per-hook notes. Hooks carry a sibling
*.md(repo-only, academic) explaining alternative implementations and why we chose ours — e.g.use-theme.md.
This is a pnpm project. Requires Node >=22.
pnpm install # install dependencies
pnpm build # build the package (tsdown)
pnpm test # run the test suite (vitest)
pnpm typecheck # tsc --noEmit
pnpm lint # eslint
pnpm format # prettier --check
pnpm check # everything: format, lint, build, typecheck, test, exports
pnpm docs:build # generate the API docs site (TypeDoc)- API reference — generated from TSDoc comments.
docs/— recipes (adding a hook, cutting a release) and architecture decision records.
MIT © Ian Goodnight