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

@statekit/react

v0.13.3-dev

Published

Readme

@statekit/react

Use typed StateKit machines in React.

Work in progress.

For more machine features, see @statekit/core

Install

npm i @statekit/react react

@statekit/react supports React 18 and later.

Add StatekitProvider near the root of every React tree that uses StateKit:

import { createRoot } from "react-dom/client"
import { StatekitProvider } from "@statekit/react"
import { App } from "./App"

createRoot(document.getElementById("root")!).render(
   <StatekitProvider>
      <App />
   </StatekitProvider>
)

Quick start

import { createMachine, createMachineContext } from "@statekit/react"

const inviteMachine = createMachine({
   data: {
      inviteUrl: "https://example.com/invite/team-42",
      copies: 0,
      message: "Ready to share",
   },
   events: {
      copyLink: async ({ draft }) => {
         draft.message = "Copying"

         await navigator.clipboard.writeText(draft.inviteUrl)

         draft.copies++
         draft.message = "Copied"
      },
   },
   selectors: {
      hasBeenCopied: ({ data }) => data.copies > 0,
   },
})

const [useInviteMachine] = createMachineContext(inviteMachine)

export const InviteLink = () => {
   const m = useInviteMachine()

   return (
      <section>
         <p>Invite link: {m.data.inviteUrl}</p>
         <button onClick={m.event.copyLink}>Copy invite link</button>
         <p>{m.data.message}</p>
         <p>
            {m.select.hasBeenCopied()
               ? `Copied ${m.data.copies} times`
               : "Not copied yet"}
         </p>
      </section>
   )
}

createMachineContext returns [useMachine, Provider]. useInviteMachine returns the machine API as m and tracks the data, state, and selectors the component reads. Every component that calls it shares the same inviteMachine.

m.event.copyLink is the machine event itself. It shows Copying immediately, waits for the clipboard, then records the copy and shows Copied. React receives both updates from one async event.

What the hook returns

The hook returns the machine itself, with reads tracked for this component:

  • m.data - current data;
  • m.event - functions that change data or state;
  • m.select - named reads of data;
  • m.state and m.STATE - optional workflow states.

m.setData, m.setState, and m.subscribeEvents are also available when you need lower-level control.

Name the hook and the value after the machine. When a component is built around one machine, m is enough:

const m = useInviteMachine()

When it uses several, give each value a specific name:

const cartMachine = useCartMachine()

Public and private machines

Machines are public by default. Their hook works in any component, and no machine provider is needed.

Make a machine private when its hook should only work below its provider:

const checkoutMachine = createMachine(
   {
      data: { step: 1 },
   },
   { private: true }
)

const [useCheckoutMachine, CheckoutProvider] = createMachineContext(checkoutMachine)

Calling useCheckoutMachine above CheckoutProvider throws. The provider controls where the machine can be used; it does not create a new copy. Every provider and hook from one createMachineContext share the same machine.

To make every machine private unless it says otherwise, call setDefaultMachineOptions once, before importing any module that creates a machine:

import { setDefaultMachineOptions } from "@statekit/react"

setDefaultMachineOptions({ private: true })

A machine created with { private: false } stays public.

Use a hook without a separate provider component

wrap mounts a machine provider around the component that calls its hook. Component props still pass through.

import { wrap } from "@statekit/react"

export const Checkout = wrap(CheckoutProvider, () => {
   const m = useCheckoutMachine()

   return <p>Step {m.data.step}</p>
})

Pass an array to mount several machine providers. The last provider becomes the outermost one:

export const Checkout = wrap(
   [CheckoutProvider, CartProvider, SessionProvider],
   () => <CheckoutView />
)

Here, SessionProvider is outermost and CheckoutProvider is innermost.

You can also place a provider directly in JSX when a machine belongs to a whole route or layout.

Reads control rerenders

Each call to useInviteMachine tracks the fields that component reads.

  • A component that reads m.data.copies rerenders when copies changes.
  • A component that reads only copies does not rerender when message changes.
  • Reading m.state watches machine state.
  • A selector watches the fields it reads, so m.select.hasBeenCopied() watches copies.

Nested objects, arrays, object keys, and array methods are tracked. Read only the data the component needs.

Date, Map, Set, RegExp, and Promise values are returned as they are. A component that reads one rerenders when it is replaced by a new instance, not when something inside it changes. Prefer plain objects, arrays, strings, numbers, and booleans in data that components read.

States

A component that reads m.state rerenders when the state changes:

const uploadMachine = createMachine({
   states: ["idle", "uploading", "done", "failed"],
   events: {
      start: ({ setState, S }) => setState(S.uploading),
      finish: ({ setState, S }) => setState(S.done),
   },
})

const [useUploadMachine] = createMachineContext(uploadMachine)

const UploadStatus = () => {
   const m = useUploadMachine()

   return m.state === m.STATE.uploading ? <p>Uploading…</p> : <p>Waiting.</p>
}

Comparing with === works while one state is active. When several states can be active at once, check with (m.state & m.STATE.uploading) !== 0. Several active states, and advanced options like transition rules, are covered in @statekit/core.

Call events outside React

Events can be called from normal functions or other modules. Mounted components still update.

await inviteMachine.event.copyLink()
inviteMachine.data.copies // latest snapshot

You can also observe every event:

const stop = inviteMachine.subscribeEvents(([name, args, success]) => {
   console.log(name, args, success)
})

await inviteMachine.event.copyLink()
stop()

Initializer and server rendering

An initializer runs once, in the browser, the first time a component uses the machine. It does not run during server rendering.

const preferencesMachine = createMachine({
   data: { theme: "system" },
   initializer: (data) => ({
      ...data,
      theme: localStorage.getItem("theme") ?? data.theme,
   }),
})

Use an initializer for data that must be created in the browser, such as localStorage values.

For large apps with many machines, @shortkit/make-lazy can delay machine creation until first use.

StateKit packages

  • @statekit/core runs the machine outside any view library.
  • @statekit/connector tracks field reads for React and other adapters.
  • @statekit/machine-message adds a small request cache built on a StateKit machine.
  • @statekit/decorations creates config wrappers that add reusable behavior to machines.
  • @statekit/decoration-tanstack-query writes a TanStack Query's data, loading state, and error into a machine.
  • @statekit/eslint-plugin reports draft changes between awaits and destructuring that rerenders on every data change.

Direct comparison

The difference is easiest to see in everyday code.

| Task | StateKit | Redux Toolkit | TanStack Query | Zustand | | ------------------ | ------------------------------------------------ | ------------------------------------------------------------- | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | Best fit | Data, events, and workflows that belong together | General app state with Redux tooling | Fetching and caching server data | Small, direct client stores | | Read in React | m.data.copies | useSelector((s) => s.copies) | query.data | useStore((s) => s.copies) | | Use as a handler | m.event.copyLink | () => dispatch(copyLink()) | () => mutation.mutate() | copyLink | | Read outside React | inviteMachine.data.copies | store.getState().copies | queryClient.getQueryData(...) | useStore.getState().copies | | Call outside React | inviteMachine.event.copyLink() | store.dispatch(copyLink()) | queryClient.fetchQuery(...) | useStore.getState().copyLink() | | Limit rerenders | Automatic from reads | Write a selector | Query subscription | Write a selector | | Async work | One async function | Thunk or RTK Query | Built in for requests | Async action | | Share as a library | Export one machine | Export actions and a reducer; the app adds it to its store | Export query helpers; the app supplies a QueryClient | Export a store or hook |

StateKit keeps client code direct: read data, call events, and use the same machine anywhere. @statekit/machine-message adds a small request cache; TanStack Query remains stronger when server caching is the main problem, and the two can be used together.

Do

  • Create a shared machine and its context outside components.
  • Add StatekitProvider near the root of the app.
  • Use const m = useInviteMachine() inside the component.
  • Read only the data the component displays.
  • Make a machine private when it must stay below a provider.
  • Call machine events from other modules when needed.

Don't

  • Don't mutate m.data directly; change data through events.
  • Don't call a private machine's hook above its provider.
  • Don't expect a provider to create a separate copy of the machine.
  • Don't call setDefaultMachineOptions after machines have been created.
  • Don't update the draft in a single event between two awaits, only at the start or at the end.

@statekit/eslint-plugin reports draft updates between awaits, and destructuring that re-renders a component on every data change.

Development

From the node directory:

pnpm --filter @statekit/react test -- --runInBand
pnpm --filter @statekit/react build