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

@uistate/react

v1.0.1

Published

React adapter for @uistate/core — usePath, useIntent, useAsync hooks and EventStateProvider

Readme

@uistate/react

React adapter for @uistate/core. Five hooks and a provider; that's the entire API.

Install

npm install @uistate/react @uistate/core react

Peer dependencies: @uistate/core >=5.0.0 and react >=18.0.0.

Quick Start

import { createEventState } from '@uistate/core';
import { EventStateProvider, usePath, useIntent } from '@uistate/react';

// Store lives outside React
const store = createEventState({
  state: { count: 0 },
});

// Business logic lives outside React
store.subscribe('intent.increment', () => {
  store.set('state.count', store.get('state.count') + 1);
});

function Counter() {
  const count = usePath('state.count');
  const increment = useIntent('intent.increment');
  return <button onClick={() => increment(true)}>Count: {count}</button>;
}

function App() {
  return (
    <EventStateProvider store={store}>
      <Counter />
    </EventStateProvider>
  );
}

API

<EventStateProvider store={store}>

Makes a store available to all descendant hooks via React Context. The store is created outside React; the provider is pure dependency injection, not a state container.

<EventStateProvider store={store}>
  <App />
</EventStateProvider>

useStore()

Returns the store from context. Throws if called outside a provider.

const store = useStore();

usePath(path)

Subscribe to a dot-path. Re-renders only when the value at that path changes. Uses useSyncExternalStore for concurrent-mode safety.

const tasks = usePath('state.tasks');
const userName = usePath('state.user.name');
const filtered = usePath('derived.tasks.filtered');

useIntent(path)

Returns a stable, memoized function that publishes a value to a path. Safe to pass as a prop without causing re-renders.

const addTask = useIntent('intent.addTask');
const setFilter = useIntent('intent.changeFilter');

// In a handler:
addTask('Buy milk');
setFilter('active');

useWildcard(path)

Subscribe to a wildcard path. Re-renders when any child of that path changes. Returns the parent object.

const user = useWildcard('state.user.*');
// Re-renders when state.user.name, state.user.email, etc. change

useAsync(path)

Async data fetching with automatic status tracking. Returns { data, status, error, execute, cancel }.

function UserList() {
  const { data, status, error, execute, cancel } = useAsync('users');

  useEffect(() => {
    execute((signal) =>
      fetch('/api/users', { signal }).then(r => r.json())
    );
  }, [execute]);

  if (status === 'loading') return <Spinner />;
  if (error) return <Error message={error} />;
  return <ul>{data?.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

Calling execute again auto-aborts the previous in-flight request. No race conditions.

Architecture

The recommended pattern is three namespaces:

| Namespace | Purpose | Hooks | |-----------|---------|-------| | state.* | Authoritative application state | usePath | | derived.* | Computed projections | usePath | | intent.* | Write-only signals from the UI | useIntent |

This gives you Model-View-Intent (MVI) inside a single store:

  • state.* is the Model
  • derived.* is the ViewModel
  • intent.* is the Controller
// Component only declares what it reads and what it publishes
function Filters() {
  const filter = usePath('state.filter');           // read
  const setFilter = useIntent('intent.changeFilter'); // write
  return <button onClick={() => setFilter('active')}>{filter}</button>;
}

Business logic lives in subscribers and is testable without React:

store.subscribe('intent.addTask', (text) => {
  const tasks = store.get('state.tasks') || [];
  store.set('state.tasks', [...tasks, { id: genId(), text }]);
});

Testing

Two-layer testing architecture:

self-test.js: Zero-dependency self-test (22 assertions). Runs automatically on npm install via postinstall. Tests the store-side patterns that the hooks consume: subscribe + get (usePath), stable setter (useIntent), wildcard subscribe (useWildcard), setAsync lifecycle (useAsync), external store contract (useSyncExternalStore compat), and batch (React 18 compat).

node self-test.js

tests/react.test.js: Integration tests via @uistate/event-test (14 tests). Tests the same patterns through createEventTest and runTests, plus type generation from React app state shapes.

npm test

| Suite | Assertions | Dependencies | |-------|-----------|-------------| | self-test.js | 22 | @uistate/core only | | tests/react.test.js | 14 | @uistate/event-test, @uistate/core |

Note: Since the React adapter uses JSX and React hooks, the self-test verifies the store-side patterns (EventState as IR). The hooks are thin wrappers around store.subscribe + useSyncExternalStore: testing the IR proves the hooks will work. JSX/React-specific behavior (re-renders, concurrent mode) requires a React test environment.

Why a separate package?

  • Zero cost if you don't use React: @uistate/core stays framework-free
  • React is a peer dependency: not bundled, no version conflicts
  • Tiny: ~50 lines of code, no dependencies beyond React and the core store

License

MIT