Skip to content

Package @reatom/extensions

@reatom/extensions

Interfaces

AbortExt

Defined in: withAbort.ts:10

Extended by

Properties

abort

abort: Action<[any]>

Defined in: withAbort.ts:11


DynamicSubscriptionExt

Defined in: withDynamicSubscription.ts:9

This interface improve .subscribe method behavior by relying it on abortVar. It performs unsubscribe automatically, when abort will occur.


SuspenseRecord

Defined in: withSuspense.ts:10

Internal suspense cache record tracking promise state. Do not use it directly, only for libraries!

Properties

kind

kind: "fulfilled" | "rejected" | "pending"

Defined in: withSuspense.ts:11

value

value: any

Defined in: withSuspense.ts:12

Type Aliases

SuspenseExt

SuspenseExt<State> = object

Defined in: withSuspense.ts:72

Extension type that adds a suspended computed atom to track resolved values from async atoms.

Type Parameters

State

State

Properties

suspended

suspended: Computed<Awaited<State>>

Defined in: withSuspense.ts:73

Variables

SUSPENSE

SUSPENSE: WeakMap<Promise<any>, SuspenseRecord>

Defined in: withSuspense.ts:19

Internal suspense cache mapping promises to their settlement state. Do not use it directly, only for libraries!


withSuspenseInit()

withSuspenseInit: {<State>(): Ext<Atom<Promise<State>, [Promise<State>]>, Atom<State, [State]>>; <Target>(cb): Ext<Target>; }

Defined in: withSuspense.ts:261

Extension that enables asynchronous initialization for synchronous atoms. This feature bridges async data loading with sync atom semantics.

During initialization, if the result is a Promise, it throws the promise (suspense pattern) and schedules setting the atom’s state when resolved. After initialization completes, the atom operates fully synchronously.

This is perfect for local-first architectures: load data asynchronously on init, then work with it synchronously. Combine with withChangeHook to sync changes back to a server or database.

Without callback: Transforms Atom<Promise<State>> into Atom<State>. The atom’s async initializer is unwrapped, and consumers receive the resolved value.

Call Signature

<State>(): Ext<Atom<Promise<State>, [Promise<State>]>, Atom<State, [State]>>

Type Parameters
State

State

Returns

Ext<Atom<Promise<State>, [Promise<State>]>, Atom<State, [State]>>

Call Signature

<Target>(cb): Ext<Target>

Type Parameters
Target

Target extends AtomLike<any, any[], any>

Parameters
cb

(state?) => AtomState<Target> | Promise<AtomState<Target>>

Returns

Ext<Target>

Examples

const userSettings = atom(async () => {
const response = await fetch('/api/settings')
return response.json()
}).extend(withSuspenseInit())
// Type: Atom<Settings> (not Atom<Promise<Settings>>)
effect(() => {
// After init completes, reads are synchronous
const settings = userSettings()
console.log(settings.theme)
})
// With callback: Provides an async initializer for any atom type, keeping the original state type.
// Local-first pattern: async init + sync operations + sync-back
const todos = atom<Todo[]>([]).extend(
withSuspenseInit(async () => {
const cached = await indexedDB.get('todos')
return cached ?? []
}),
withChangeHook((newState) => {
// Sync changes back to storage
indexedDB.set('todos', newState)
}),
)
// Typed async init with custom default
const profile = atom<{ username: string; age: number }>(
throwAbort,
).extend(
withSuspenseInit(async () => {
const data = await fetchProfile()
return data ?? { username: 'guest', age: 0 }
}),
)
@overload
@overload

Param

Async or sync initializer function. Receives the current init state and returns the new state (or Promise of it).

Returns

Extension that unwraps Atom<Promise<State>> to Atom<State>

Returns

Extension that initializes the atom with the callback result

Functions

addCallHook()

addCallHook<Target>(target, cb): Unsubscribe

Defined in: withChangeHook.ts:257

Dynamically adds a call hook to an existing action and returns a function to remove it.

Unlike withCallHook which is applied at action definition time, addCallHook allows you to add and remove hooks at runtime. This is useful for temporary subscriptions, conditional hook behavior, or when integrating with external systems that need to be connected and disconnected dynamically.

This feature is rarely needed, you should prefer using effect with getCalls or take instead.

Type Parameters

Target

Target extends Action<any[], any>

The action type

Parameters

target

Target

The action to attach the hook to

cb

(payload, params) => void

Callback fired when the action is called

Returns

Unsubscribe

Unsubscribe function to remove this specific hook

See

withCallHook For adding hooks at action definition time


addChangeHook()

addChangeHook<T>(target, cb): Unsubscribe

Defined in: withChangeHook.ts:118

Dynamically adds a change hook to an existing atom and returns a function to remove it.

Unlike withChangeHook which is applied at atom definition time, addChangeHook allows you to add and remove hooks at runtime. This is useful for temporary subscriptions or when you need conditional hook behavior that can be enabled/disabled dynamically.

This feature is rarely needed, you should prefer using effect with ifChanged or take instead.

Type Parameters

T

T extends AtomLike<any, any[], any>

The atom type

Parameters

target

T

The atom to attach the hook to

cb

(state, prevState?) => void

Callback fired when state changes

Returns

Unsubscribe

Unsubscribe function to remove this specific hook

See

withChangeHook For adding hooks at atom definition time


isInit()

isInit(): boolean

Defined in: withInit.ts:22

Checks if the current execution context is within the initialization of the current atom.

Returns

boolean

True if currently in the initialization phase, false otherwise

Example

const search = atom('', 'search').extend(withSearchParams('search'))
const page = atom(1, 'page').extend(
withSearchParams('page'),
withComputed((state) => {
search() // subscribe to the search changes
// do NOT drop the persisted state on init
return isInit() ? state : 1
}),
)

settled()

settled<Result, Fallback>(promise, fallback?): Result | Fallback

Defined in: withSuspense.ts:39

Checks if a promise is settled and returns its value or fallback. If the promise is fulfilled, returns the resolved value. If the promise is rejected, throws the error. If the promise is pending, returns the fallback value (defaults to undefined).

Uses an internal WeakMap cache to track promise states across calls.

Type Parameters

Result

Result

Fallback

Fallback = undefined

Parameters

promise

The promise or synchronous value to check

Result | Promise<Result>

fallback?

Fallback

The value to return if the promise is still pending

Returns

Result | Fallback

The resolved value if fulfilled, throws if rejected, or fallback if pending

Example

const promise = Promise.resolve(42)
await promise
const value = settled(promise) // 42

suspense()

suspense<State>(target): Awaited<State>

Defined in: withSuspense.ts:194

Helper function to access the suspended value of an atom. Automatically applies withSuspense() extension if the atom doesn’t already have it.

This function:

  • Returns the resolved value if the promise is fulfilled
  • Throws the promise if it’s still pending (for Suspense boundaries)
  • Throws the error if the promise is rejected

Type Parameters

State

State

Parameters

target

AtomLike<State>

The atom to get the suspended value from

Returns

Awaited<State>

The resolved value (Awaited), or throws a promise/error

Remarks

If withSuspense is already applied with different preserve options, the behavior may be inconsistent. Consider applying withSuspense() explicitly to control options.

Example

const data = computed(async () => {
const response = await fetch('/api/data')
return response.json()
}, 'data')
// Automatically applies withSuspense() and returns suspended value
const result = computed(() => {
try {
return suspense(data) // throws promise if pending
} catch (promise) {
if (promise instanceof Promise) {
// Handle pending state
return undefined
}
throw promise // Re-throw errors
}
}, 'result')

withAbort()

withAbort(strategy): AssignerExt<AbortExt>

Defined in: withAbort.ts:14

Parameters

strategy

"last-in-win" | "first-in-win"

Returns

AssignerExt<AbortExt>


withCallHook()

withCallHook<Target>(cb): Ext<Target>

Defined in: withChangeHook.ts:204

Executes a callback whenever the target action is called.

This extension enables you to react to action invocations, making it invaluable for creating stable connections between independent features. The hook fires in the “Hooks” phase (after Updates, before Computations) and receives both the action’s return value and its parameters.

When to use:

  • Creating stable cross-module connections that react to specific actions
  • Tracking action calls for analytics, logging, or debugging
  • Triggering side effects in response to action completions
  • Coordinating behavior between independent features without coupling them
  • Implementing event-driven communication patterns

When NOT to use:

  • In dynamic features, like from computed factories (take or effect and getCalls instead)
  • When you can achieve the same goal with direct action composition

For actions extended with withAsync, you can also hook into .onFulfill, .onReject, or .onSettle to react to async completion events.

Type Parameters

Target

Target extends Action<any[], any>

The action type being extended

Parameters

cb

(payload, params) => void

Callback fired when action is called. Receives:

  • payload - The return value of the action
  • params - The parameters passed to the action as an array

Returns

Ext<Target>

Extension function to be used with .extend()

Examples

// Cross-module coordination: Analytics tracking
// In checkoutModule.ts
export const submitOrder = action(async (order) => {
const result = await api.submitOrder(order)
return result
}, 'submitOrder')
// In analyticsModule.ts
import { submitOrder } from './checkoutModule'
submitOrder.extend(
withCallHook((promise, params) => {
const [order] = params
analytics.track('new_order', {
orderId: order.id,
total: order.total,
})
}),
)
// Stable feature connection: Form submission tracking
const fetch = action(async (param: number) => {
const data = await api.fetch(param)
return data
}, 'fetch').extend(withAsync())
fetch.onFulfill.extend(
withCallHook((call) => {
console.log('Fetch completed', call.payload)
}),
)

Throws

If callback is not a function

Throws

If applied to an atom instead of an action

See

  • addCallHook For dynamically adding/removing call hooks
  • withChangeHook For reacting to atom state changes instead
  • withAsync For async action lifecycle hooks (onFulfill, onReject, onSettle)

withChangeHook()

withChangeHook<Target>(cb): Ext<Target>

Defined in: withChangeHook.ts:70

Executes a callback whenever the target atom’s state changes.

This extension is essential for creating stable, declarative connections between independent modules or features. The hook fires in the “Hooks” phase of Reatom’s lifecycle (after Updates, before Computations), making it perfect for triggering side effects or synchronizing state across module boundaries.

When to use:

  • Creating stable connections between features that shouldn’t depend on each other directly
  • Triggering validation when a field’s value or state changes
  • Syncing derived state in response to source state changes
  • Managing side effects like DOM updates or analytics based on state changes
  • Coordinating behavior across module boundaries without coupling them

When NOT to use:

  • In dynamic features, like from computed factories (use take or effect and ifChanged instead)
  • When a regular computed dependency would suffice
  • For connection/disconnection events (use withConnectHook instead)

The callback receives the new state and previous state. It only fires when the state actually changes (referential inequality check via Object.is). The callback executes within the same reactive context, so you can safely call other atoms and actions.

Type Parameters

Target

Target extends AtomLike<any, any[], any>

The atom type being extended

Parameters

cb

(state, prevState) => void

Callback fired when state changes. Receives:

  • state - The new state value
  • prevState - The previous state value (undefined on first change)

Returns

Ext<Target>

Extension function to be used with .extend()

Examples

// Basic usage: React to atom state changes
const theme = reatomEnum(['light', 'dark', 'system']).extend(
withChangeHook((state, prevState) => {
document.body.classList.remove(prevState)
document.body.classList.add(state)
}),
)
// Stable feature connection: Analytics tracking
// In userModule.ts
export const userAtom = atom({ id: null, name: '' }, 'user')
// In analyticsModule.ts
import { userAtom } from './userModule'
userAtom.extend(
withChangeHook((user, prevUser) => {
if (user.id !== prevUser?.id) {
analytics.identify(user.id, { name: user.name })
}
}),
)

Throws

If callback is not a function

See


withComputed()

withComputed<Target>(computed, options?): Ext<Target>

Defined in: withComputed.ts:25

A middleware extension that enhances an atom with computed capabilities.

Type Parameters

Target

Target extends AtomLike<any, any[], any>

The target atom or action type to be extended with computed functionality.

Parameters

computed

(state) => AtomState<Target>

A function that computes the new state based on the current state.

options?

Configuration options. Default is {}

tail?

boolean = true

Determines the order of the passed computed calling. ATTENTION: use false only for computed with fixed size of dependencies. Default is true

Returns

Ext<Target>

The extended atom or action with computed functionality.


withConnectHook()

withConnectHook<Target>(cb): Ext<Target>

Defined in: withConnectHook.ts:10

Type Parameters

Target

Target extends AtomLike<any, any[], any>

Parameters

cb

(target) => void | Unsubscribe | Promise<void | Unsubscribe>

Returns

Ext<Target>


withDisconnectHook()

withDisconnectHook<Target>(cb): Ext<Target>

Defined in: withConnectHook.ts:38

Type Parameters

Target

Target extends AtomLike<any, any[], any>

Parameters

cb

(target) => void

Returns

Ext<Target>


withDynamicSubscription()

withDynamicSubscription<Target>(): (target) => Target & DynamicSubscriptionExt

Defined in: withDynamicSubscription.ts:12

Type Parameters

Target

Target extends AtomLike<any, any[], any>

Returns

(target): Target & DynamicSubscriptionExt

Parameters
target

Target

Returns

Target & DynamicSubscriptionExt


withInit()

withInit<Target>(init): Ext<Target>

Defined in: withInit.ts:57

Define dynamically computed initial value for an atom.

Typically, you can use just an init callback in atom first argument: atom(() => new Date()). But if you need to add initial callback after the atom creation, so there this extensions is useful.

Type Parameters

Target

Target extends AtomLike<any, any[], any>

The atom type that extends AtomLike

Parameters

init

The initial value or a function that returns the initial value based on current state

AtomState<Target> | (state) => AtomState<Target>

Returns

Ext<Target>

An extension that can be applied to an atom

Examples

const something = reatomSomething().extend(
withInit((initState) => ({ ...initState, ...additions })),
)
const myData = atom(null, 'myData')
if (meta.env.TEST) {
myData.extend(withInit(mockData))
}

withInitHook()

withInitHook<Target>(hook): Ext<Target>

Defined in: withInit.ts:95

Extension that runs the passed hook when the atom is initialized.

Type Parameters

Target

Target extends AtomLike<any, any[], any>

The atom type that extends AtomLike

Parameters

hook

(initState) => any

A function to be called with the initial state during initialization

Returns

Ext<Target>

An extension that can be applied to an atom

Example

const userAtom = atom({ id: 1, name: 'John' }).extend(
withInitHook((initState) => {
// Perform any setup logic here
analytics.track('user_loaded', initState)
}),
)

withMemo()

withMemo<Target>(isEqual): Ext<Target>

Defined in: withMemo.ts:6

Type Parameters

Target

Target extends AtomLike<any, any[], any>

Parameters

isEqual

(prevState, nextState) => boolean

Returns

Ext<Target>


withSuspense()

withSuspense<Target>(options): Ext<Target, SuspenseExt<AtomState<Target>>>

Defined in: withSuspense.ts:113

Extension that adds suspense support to async atoms. Creates a suspended computed atom that tracks the resolved value of promises and throws the promise when pending (for React Suspense compatibility).

The suspended atom will:

  • Return the resolved value immediately if the promise is already fulfilled
  • Throw the promise if it’s still pending (allowing Suspense boundaries to catch it)
  • Propagate errors if the promise is rejected
  • Automatically update when the promise resolves

Type Parameters

Target

Target extends AtomLike<any, any[], any> & Partial<SuspenseExt<AtomState<Target>>>

Parameters

options

Configuration options

preserve?

boolean = false

If true, preserves the previous state when suspending instead of throwing immediately. Useful for preventing flickering in UI.

Returns

Ext<Target, SuspenseExt<AtomState<Target>>>

An extension that adds the suspended computed atom

Example

const data = computed(async () => {
const response = await fetch('/api/data')
return response.json()
}, 'data').extend(withSuspense())
// Subscribe to resolved values
subscribe(data.suspended, (value) => {
console.log('Resolved:', value)
})
// Use in React component with Suspense
function Component() {
const value = useAtom(data.suspended) // throws promise if pending
return <div>{value}</div>
}

withSuspenseRetry()

withSuspenseRetry<T>(): Ext<T>

Defined in: withSuspenseRetry.ts:28

Creates a mixin that retries an async action when it fails coz of a suspension

This mixin wraps an async action to automatically retry it when a Promise is thrown, which indicates a suspension. It will keep retrying until the action completes successfully or throws a non-Promise error.

⚠️ Be careful with non-idempotent operations inside the action body, as they may be executed multiple times during retries. It’s recommended to carefully plan the execution logic to handle potential retries safely.

Type Parameters

T

T extends Action<unknown[], Promise<unknown>>

Returns

Ext<T>

The same passed action

Example

const fetchUserBooks = action(async () => {
const id = user().id // `user` is a suspended atom
const response = await fetch(`/api/users/${id}/books`)
return response.json()
}).extend(withSuspenseRetry())