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

@icydotdev/pocket

v1.0.7

Published

Pocket-sized React Context syntax. One call, zero ceremony.

Readme

pocket

Pocket-sized React Context syntax. One call, zero boilerplate.

npm version npm downloads license

Quick Start · Features · Usage · FAQ · Comparison · Contributing


Stop declaring contexts. Stop building Provider components. Stop wiring up useContext and throwing on undefined. pocket collapses React Context boilerplate into one call — and stays a thin layer over the Context API. No store, no atoms, no selectors, no magic. If you know React Context, you already know how this behaves.

Quick Start

npm i @icydotdev/pocket
// 1. One call returns a typed Provider AND a hook to consume it:
const [ThemeProvider, useTheme] = createPocket(() => useState('light'));

// 2. Drop the Provider anywhere — same as vanilla Context:
<ThemeProvider>
  <Toggle />
</ThemeProvider>;

// 3. Consume from any descendant — also same as vanilla Context:
function Toggle() {
  const [theme, setTheme] = useTheme();

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      {theme}
    </button>
  );
}

Features

  • One-call ContextcreatePocket(setup) returns [Provider, useValues]. No createContext, no useContext, no manual undefined throw, no hand-written wrapper component
  • Tuple passthrough — Return useState directly and consumers destructure exactly like useState. Single-value contexts become one-liners
  • Provider props — The setup function takes whatever props you pass to the Provider. Per-instance config for free
  • Per-instance state — Each <Provider/> mount runs its own setup. Same Context, independent state per mount. Nesting works like normal Context
  • Named errors — Pass a name once and get descriptive error messages: "Theme consumer called outside <Theme.Provider>" instead of a generic throw
  • React DevTools labels — Provider shows as <Theme.Provider> in the tree, consumer hook registers as useTheme
  • Inline setup — Pass an arrow function directly. No separate function useCounter() definition required first
  • Full TypeScript inference — Types inferred from the setup function's return. Zero annotations
  • Thin layer over Context — Same render semantics, same SSR story, same mental model. If you need fine-grained re-renders, reach for Zustand or Jotai — this stays close to vanilla Context on purpose
  • Tiny bundle — Single tree-shakeable export, zero runtime dependencies

Before / After

The classic 20-line createContext + useContext + ThemeProvider + useTheme boilerplate collapses to one line.

Before — vanilla Context

// theme-context.tsx
import { createContext, useContext, useState, type ReactNode } from 'react';

type ThemeCtx = {
  theme: 'light' | 'dark';
  setTheme: (t: 'light' | 'dark') => void;
};
const ThemeContext = createContext<ThemeCtx | undefined>(undefined);

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const v = useContext(ThemeContext);
  if (!v) throw new Error('useTheme must be used inside ThemeProvider');
  return v;
}

After — pocket

// theme.ts
import { useState } from 'react';
import { createPocket } from '@icydotdev/pocket';

export const [ThemeProvider, useTheme] = createPocket(() =>
  useState<'light' | 'dark'>('light'),
);

~22 lines of vanilla Context → 1. Consumers destructure [theme, setTheme] exactly like useState.

Usage

Basic

createPocket takes a setup function. It runs inside the Provider on every render — call any hooks you want (useState, useEffect, useReducer, custom hooks). Return whatever consumers should see.

import { useState, useEffect } from 'react';
import { createPocket } from '@icydotdev/pocket';

export const [DashboardProvider, useDashboard] = createPocket(() => {
  const [filter, setFilter] = useState('');
  const [sortBy, setSortBy] = useState<'date' | 'name'>('date');

  useEffect(() => {
    document.title = `Dashboard — ${filter || 'all'}`;
  }, [filter]);

  return { filter, setFilter, sortBy, setSortBy };
});
function Search() {
  const { filter, setFilter } = useDashboard();
  return <input value={filter} onChange={(e) => setFilter(e.target.value)} />;
}

Tuple passthrough

Return a tuple → consumers destructure a tuple. Single-value contexts become one-liners:

export const [ThemeProvider, useTheme] = createPocket(() =>
  useState<'light' | 'dark'>('light'),
);

function ThemeToggle() {
  const [theme, setTheme] = useTheme(); // mirrors useState
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      {theme}
    </button>
  );
}

Provider props

The setup function can take props. The Provider forwards everything except children:

const [Counter, useCounter] = createPocket(
  ({ initial = 0 }: { initial?: number }) => {
    const [n, setN] = useState(initial);

    return { n, setN };
  },
);

<Counter initial={42}>
  <Display />
</Counter>;

Per-instance state

Multiple <Provider/> mounts each run their own setup → independent state. Same Context, different values per mount.

<Counter><Display /></Counter>
<Counter initial={100}><Display /></Counter>

TypeScript

Types inferred from the setup function's return. Don't annotate.

const [, useValues] = createPocket(() => ({ a: 1, b: 'x', c: true }));
const { a, b, c } = useValues();
//      ^number ^string ^boolean

const [, useTheme] = createPocket(() => useState<'light' | 'dark'>('light'));
const [theme, setTheme] = useTheme();
//     ^'light'|'dark'  ^Dispatch<SetStateAction<...>>

Name your pocket (recommended)

Pass a name as the first argument. Two upgrades for free:

export const [ThemeProvider, useTheme] = createPocket('Theme', () => {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  return { theme, setTheme };
});

1. Better error messages. When a consumer calls outside the Provider:

Error: Theme consumer called outside <Theme.Provider>.
Wrap the calling component in <Theme.Provider> or check your tree.

vs unnamed:

Error: Pocket consumer called outside <Pocket.Provider>.
Wrap the calling component in <Pocket.Provider> or check your tree.

2. React DevTools labels. The Provider shows as <Theme.Provider> in the component tree, not <ContextProvider>. The consumer hook registers as useTheme.

Skip the name and everything still works: the default is 'Pocket'.

When NOT to use it

| You need... | Use this instead | | -------------------------------------- | ------------------------------------------------------------------------------------------------------- | | Cross-component state with no Provider | Zustand | | Fine-grained re-renders / selectors | Jotai or use-context-selector | | Server Components state | RSC + props |

Comparison

| | createPocket | vanilla Context | constate | Zustand | | ---------------------------- | -------------- | --------------- | ------------------------------------------------ | ----------------- | | Lines of boilerplate | ~1 | ~20 | ~6 | ~5 | | Provider required | yes | yes | yes | no | | Tuple passthrough | yes | yes | no (forces object) | n/a | | Named Provider in DevTools | auto | manual | manual | n/a | | Named error messages | auto | manual | generic | n/a | | Inline setup (no named hook) | yes | n/a | no | yes | | Selectors | no | no | yes | yes | | Thin layer over Context | yes | yes | yes | no (custom store) | | Bundle size | tiny | 0 | tiny | small |

vs constate

Same factory shape, sharper edges:

  • Tuple passthroughcreatePocket(() => useState(...)) works. Constate forces an object wrap.
  • Auto-named Provider + errors'Theme' once, get <Theme.Provider> in DevTools and named errors for free.
  • Inline setup — pass an arrow directly, no function useCounter() {...} definition required first.

vs Zustand / Jotai

pocket stays close to vanilla Context on purpose — same render semantics, same mental model, same SSR story. If you need fine-grained re-renders, reach for Zustand/Jotai. If you want Context without the ceremony, this is it.

FAQ

Is it a hook?

createPocket is a module-level factory. The useValues it returns IS a hook — call it at the top of a component. eslint-plugin-react-hooks enforces rules-of-hooks correctly.

Can the setup function call hooks?

Yes. It runs inside the Provider on each render, so anything legal in a custom hook is legal there.

Multiple Providers?

Each <Provider/> mount runs its own setup → independent state. Nesting works like normal Context: inner overrides outer.

Why no value prop on the Provider?

Values come from the setup function. The Provider accepts any props the setup function declares, plus children.

Why no selectors?

By design. pocket is a thin layer over Context — same render semantics. If you need selector-based re-render avoidance, use use-context-selector on top, or pick Jotai/Zustand.

Does it support React Server Components?

Not directly. Like all React Context, pocket needs "use client". RSC has its own state-passing model (props, server actions).

Roadmap

  • DevTools panel — small inspector showing active pockets + current values
  • Codemod — npx @icydotdev/pocket migrate rewrites vanilla createContext files
  • Selector hook via useSyncExternalStore for opt-in fine-grained re-renders
  • Async-state primitive — createPocketAsync(promise) exposing { data, loading, error }
  • CLI scaffolder — npx @icydotdev/pocket add Theme drops a typed Theme context into your project

Contributing

Contributions welcome. Open an issue or PR.

git clone https://github.com/icydotdev/pocket.git
cd pocket
npm install
npm test

License

MIT © Sam Kavanagh