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

react-cosmic

v1.2.0

Published

React state that persists across refreshes, syncs across tabs, and survives crashes

Readme

React Cosmic

CI npm version

React state that persists across refreshes, syncs across tabs, and survives crashes. No server required.

const [draft, setDraft] = useOrbit('email-draft', '');
// That's it. Survives refresh. Syncs across tabs. Zero config.

The problem

  • User refreshes the page mid-form → data gone
  • User opens two tabs → state conflicts and overwrites
  • User's browser crashes → draft email lost
  • You want multiplayer → now you're building conflict resolution

The solution

React Cosmic gives you hooks that work like useState but persist to IndexedDB and sync across tabs automatically. Built on Yjs CRDTs, so conflicts resolve themselves — even with real-time collaboration.

Install

bun add react-cosmic  # or npm/pnpm/yarn

Quick start

Wrap your app:

import { OrbitProvider } from 'react-cosmic';

function App() {
  return (
    <OrbitProvider storeId="my-app">
      <YourStuff />
    </OrbitProvider>
  );
}

Use the hooks:

import {
  useOrbit,
  useOrbitText,
  useOrbitObject,
  useOrbitArray,
  useOrbitUndoManager,
  useOrbitStatus,
  useOrbitCircuit,
  useOrbitAwareness,
  useSetLocalAwareness
} from 'react-cosmic';

function Form() {
  const [email, setEmail] = useOrbit('email', '');
  const [bio, setBio] = useOrbitText('bio', '');
  const [profile, updateProfile] = useOrbitObject('profile', {
    name: '',
    age: 0
  });

  return (
    <form>
      <input
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <textarea
        value={bio}
        onChange={(e) => setBio(e.target.value)}
      />
      <input
        value={profile.name}
        onChange={(e) => updateProfile({ name: e.target.value })}
      />
    </form>
  );
}

That's it. Now open two tabs and watch them sync. Close the browser and reopen it. Your data is still there.

API

<OrbitProvider>

Put this around your app or component tree.

<OrbitProvider
  storeId="unique-id"           // required - same ID = shared state
  enableStorage={true}          // optional - persist to IndexedDB (default: true)
  enableTabSync={true}          // optional - sync across tabs (default: true)
  persistDebounceMs={300}       // optional - debounce saves (default: 300)
  websocketUrl="ws://localhost:1234"  // optional - sync across devices/users
  websocketOptions={{           // optional - WebSocket config
    retryDelay: 3000,           // reconnect delay in ms
    protocols: ["my-protocol"]  // WebSocket protocols
  }}
>

useOrbit(key, initialValue)

Like useState but it remembers. Use for primitives and simple values.

const [count, setCount] = useOrbit('count', 0);
const [name, setName] = useOrbit('name', '');
const [enabled, setEnabled] = useOrbit('enabled', false);

useOrbitText(key, initialValue?)

For text fields. Uses Yjs Text internally for proper collaborative editing.

const [content, setContent] = useOrbitText('doc-content', '');

useOrbitObject(key, initialValue)

For nested objects. Lets you update individual properties without replacing the whole thing.

const [user, updateUser] = useOrbitObject('user', {
  name: '',
  email: '',
  age: 0
});

updateUser({ age: 25 });  // only updates age, keeps name and email

useOrbitArray<T>(key, initialValue?)

For ordered lists. Concurrent inserts and deletes from multiple clients merge correctly.

const [todos, { push, insert, remove, clear }] = useOrbitArray<string>('todos', []);

push('Buy milk');           // append
insert(0, 'First item');    // insert at index
remove(1);                  // remove at index
clear();                    // remove all

useOrbitUndoManager(key, scope?)

Undo/redo scoped to a single Orbit key. Operations within 500ms are grouped into one step.

const [content, setContent] = useOrbitText('doc', '');
const { undo, redo, canUndo, canRedo } = useOrbitUndoManager('doc', 'text');

// scope: 'text' (default) | 'map' | 'array'

useOrbitStatus()

Track WebSocket connection status. Returns 'connected', 'connecting', or 'disconnected'.

const status = useOrbitStatus();

return <div>Status: {status}</div>;

Only useful when websocketUrl is configured. Returns 'disconnected' if WebSockets aren't enabled.

useOrbitAwareness<T>()

Read presence data for all connected clients. Returns a Map<number, T> where the key is the client ID and the value is their awareness state.

interface UserPresence {
  name: string;
  color: string;
  cursor?: number;
}

const users = useOrbitAwareness<UserPresence>();

return (
  <div>
    {Array.from(users.values()).map(user => (
      <div key={user.name}>{user.name}</div>
    ))}
  </div>
);

Only works when websocketUrl is configured.

useOrbitCircuit()

Returns true when the WebSocket circuit breaker has tripped (stopped reconnecting after repeated failures). The app continues to work with local + tab sync.

const circuitOpen = useOrbitCircuit();

if (circuitOpen) {
  return <Banner>Working offline — server unreachable</Banner>;
}

useSetLocalAwareness<T>(state)

Broadcast your presence state to all connected clients. Automatically cleans up on unmount.

interface UserPresence {
  name: string;
  color: string;
}

function MyComponent() {
  const [profile] = useOrbit<UserPresence>('profile', {
    name: 'Alice',
    color: '#ff0000'
  });

  useSetLocalAwareness(profile);

  return <div>Your name: {profile.name}</div>;
}

Only works when websocketUrl is configured.

Run locally

Basic form demo (local persistence and tab sync):

git clone https://github.com/JR-G/react-cosmic
cd react-cosmic
bun install
bun run demo

Open multiple tabs to see cross-tab sync in action.

Collaboration demo (real-time multi-user sync with presence):

Terminal 1 - start the WebSocket server:

bun run demo:server

Terminal 2 - start the demo:

bun run demo:collab

Open multiple browsers to see real-time collaboration with presence indicators and remote cursors.

When to use this

  • Forms that shouldn't lose data
  • Draft content (emails, posts, documents)
  • User preferences that persist
  • Any state you want to survive refreshes
  • Multi-tab applications
  • Real-time collaboration (with WebSocket server)
  • Cross-device sync

Not for:

  • Replacing your backend (it's a sync layer, not a database)
  • Massive datasets (it's in-browser storage)

Why not just use...

localStorage / zustand persist / jotai storage? They don't handle cross-tab sync properly. Open two tabs, edit in both, and one overwrites the other. React Cosmic uses CRDTs — edits merge automatically, no data loss.

Plain Yjs? You can, but you'll write a lot of boilerplate. React Cosmic gives you useState-like hooks with persistence and sync built in.

A backend? Sometimes you don't need one. Drafts, preferences, and form state can live entirely in the browser. When you do need sync, just add a WebSocket URL — same API, now multiplayer.

Server sync (CRDT-powered collaboration)

Want to sync across devices or add real-time multiplayer? Run a WebSocket server. The same CRDT that syncs your tabs now syncs across devices and users, with automatic conflict resolution.

The simplest option is using bunx:

bunx y-websocket-server --port 1234

Or install globally:

bun install -g y-websocket
y-websocket-server --port 1234

Then connect your app:

<OrbitProvider
  storeId="my-app"
  websocketUrl="ws://localhost:1234"
>
  <YourStuff />
</OrbitProvider>

Now users on different devices see each other's changes in real-time. The CRDT handles all conflict resolution automatically - whether it's two tabs, two users, or twenty.

For production, you'll want auth, persistence, and proper scaling. Check out y-websocket docs, PartyKit, or other hosted Yjs providers.

Docs

License

MIT