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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@reatom/react

v1000.0.0

Published

Reatom adapter for React

Downloads

3,523

Readme

Reatom adapter for react.

Note, that you don't require this adapter for simple usages, native useSyncExternalStore will be enough!

import { useSyncExternalStore } from 'react'
import { atom } from '@reatom/core'

export const page = atom(0, 'page').extend((target) => ({
  next: () => target.set((state) => state + 1),
  prev: () => target.set((state) => Math.max(0, state - 1)),
}))

export const Paging = () => {
  const state = useSyncExternalStore(page.subscribe, page)

  return (
    <span>
      <button onClick={page.prev}>prev</button>
      {state}
      <button onClick={page.next}>next</button>
    </span>
  )
}

Installation

npm i @reatom/react

Read the handbook first for production usage.

Binding Atoms to Components

Reatom offers powerful ways to integrate state management directly into your React components, ensuring reactivity and proper lifecycle management.

reatomComponent

The primary API to bind atoms and actions to a component's lifetime is reatomComponent. It wraps your regular React component function, placing it within a Reatom reactive context.

Features:

  • Reactive Reads: Simply call an atom (myAtom()) within the component function to read its value and subscribe to updates. The component will automatically re-render when the atom changes.
  • Standard React: Use any other React hooks (useState, useEffect, etc.), accept props, and return any valid ReactNode as usual.
  • Context Preservation: Event handlers should be wrapped with wrap() (e.g., onClick={wrap(myAction)}) to preserve the reactive context, especially for async operations or actions updating state.
  • No Hooks Rules for Atoms: Call and subscribe to atoms conditionally within your render logic without violating React's rules of hooks.
  • Automatic Cleanup: Integrates with Reatom's abort context. Effects or async operations triggered from within the component (using wrap or implicitly by actions) are automatically aborted if the component unmounts before completion, preventing race conditions and memory leaks.
import { atom, wrap } from '@reatom/core'
import { reatomComponent } from '@reatom/react'

export const page = atom(0, 'page').extend((target) => ({
  next: () => target((state) => state + 1),
  prev: () => target((state) => Math.max(0, state - 1)),
}))

// Simple component reading and updating global state
export const Paging = reatomComponent(
  () => (
    <span>
      <button onClick={wrap(page.prev)}>prev</button> {/* Use wrap */}
      {page()} {/* Read atom value */}
      <button onClick={wrap(page.next)}>next</button> {/* Use wrap */}
    </span>
  ),
  'Paging',
) // Naming the component is crucial for debugging!

// Component accepting props (including atoms)
type CounterProps = {
  label: string
  count: Atom<number>
  increment: Action<[], number>
}

export const Counter = reatomComponent<CounterProps>(
  ({ label, count, increment }) => (
    <div>
      {label}: {count()}
      <button onClick={wrap(increment)}> + </button>
    </div>
  ),
  'Counter',
)

// Conditional rendering based on atom values
export const SomeList = reatomComponent(
  () =>
    isLoading() ? ( // Read atom conditionally
      <span>Loading...</span>
    ) : (
      <ul>
        {list().map(
          (
            el, // Read another atom
          ) => (
            <li key={el.id}>{el.text}</li>
          ),
        )}
      </ul>
    ),
  'SomeList',
)

Do not forget to put the component name to the second argument, it will increase your feature debug experience a lot!

Unmount Behavior

A key feature of reatomComponent is its integration with Reatom's abort mechanism. When a reatomComponent unmounts:

  1. Its associated reactive context is aborted.
  2. Any pending async operations initiated within that context (e.g., await wrap(fetch(...)), await wrap(sleep(...))) are automatically cancelled.
  3. Any active effect primitives created within its context are automatically cleaned up.

This robust cleanup prevents common issues like trying to update state on unmounted components and avoids memory leaks from lingering subscriptions or timers. If you need an operation to survive component unmount (e.g., analytics), use spawn from the core package.

reatomFactoryComponent (Recommended for Local State/Effects)

While reatomComponent is great for reading atoms state, reatomFactoryComponent is the recommended pattern for components that need their own local, encapsulated state and side effects.

It separates the component logic into two parts:

  1. Factory Function: Runs once when the component instance is created. This is where you define local atoms, actions, and effects specific to this component instance. It receives the component's initial props.
  2. Render Function: Runs on every render, just like a regular React component function. It has access to the atoms and actions created in the factory scope and the current props.

Benefits:

  • True Encapsulation: Local state and effects are tied to the component instance, not shared globally.
  • Lifecycle Management: The factory scope provides a natural place for setup logic.
  • Perfect for effect: effect primitives created in the factory are automatically cleaned up when the component unmounts, making it ideal for managing local subscriptions, timers, animations, etc.
  • Stable References: Atoms, actions or any other functions created in the factory have stable references across renders.
import { atom, action, effect, wrap, sleep } from '@reatom/core'
import { reatomFactoryComponent } from '@reatom/react'

// Example: A self-contained counter component
const Counter = reatomFactoryComponent<{ initialCount: number; step?: number }>(
  // 1. Factory Function (runs once per instance)
  (initProps) => {
    // Note that the props will not change in this initialization scope.
    const step = initProps.step ?? 1
    // Create local atom specific to this Counter instance
    const count = atom(initProps.initialCount, 'localCount')
    // Create local action
    const increment = action(() => count((c) => c + step), 'increment')
    const decrement = action(() => count((c) => c - step), 'decrement')

    // Example: Log changes (effect cleans up automatically)
    effect(() => {
      const currentCount = count()
      console.log(`Counter ${initProps.initialCount} changed to:`, currentCount)
      // Cleanup function (optional, runs before next effect run or on unmount)
      return () =>
        console.log(
          `Counter ${initProps.initialCount} leaving state:`,
          currentCount,
        )
    }, 'logEffect')

    // Return the render function
    return (props) => (
      <div>
        Count (Initial: {props.initialCount}, Step: {props.step ?? 1}):{' '}
        {count()}
        <button onClick={wrap(decrement)}>-</button>
        <button onClick={wrap(increment)}>+</button>
      </div>
    )
  },
  'Counter', // Name the factory component!
)

// Usage:
// <Counter initialCount={10} />
// <Counter initialCount={0} step={5} />

Example: Using effect for Auto-Cleaning

reatomFactoryComponent combined with effect is excellent for managing resources that need cleanup. It is more powerful and precise primitive than useEffect, as it isn't coupled with rerenders.

import { atom, effect, wrap, sleep, isAbort } from '@reatom/core'
import { reatomFactoryComponent } from '@reatom/react'

const IntervalLogger = reatomFactoryComponent<{ intervalMs: number }>(
  ({ intervalMs }) => {
    const tick = atom(0, 'tick')

    // This effect runs a timer and cleans it up automatically on unmount
    effect(async () => {
      while (true) {
        // sleep respects the abort context
        await wrap(sleep(intervalMs))
        tick((t) => t + 1)
      }
    }, 'intervalEffect')

    return (props) => (
      <div>
        Interval ({props.intervalMs}ms) Ticks: {tick()}
      </div>
    )
  },
  'IntervalLogger',
)

// Usage:
// <IntervalLogger intervalMs={1000} />
// When this component unmounts, the interval stops automatically.

reatomFactoryComponent provides a robust and elegant way to build stateful, effectful components with automatic lifecycle management, leveraging the power of Reatom's core primitives like atom and effect.

Setup context

Optionally, you need to set up the main context once and wrap your application in a provider at the top level. This is required if you have called clearStack() (recommended).

import { context, connectLogger, clearStack } from '@reatom/core'
import { reatomContext } from '@reatom/react'
import { Main } from './path/to/an/Main'

// Recommended: Disable default context for predictability
clearStack()

const rootFrame = context.start()
if (import.meta.env.DEV) {
  rootFrame.run(connectLogger)
}

export const App = () => (
  <reatomContext.Provider value={rootFrame}>
    <Main />
  </reatomContext.Provider>
)