Skip to content

Custom operator

All atoms and actions by default includes pipe operator for simple extending and composing. You can create your own operators to improve your code and modularize it.

We assume that you already read core docs and naming conventions.

Prefix and types

Operator for the pipe function should starts with a verb.

import { Atom, CtxSpy } from '@reatom/core'

declare function mapState<T, Res>(
  mapper: Fn<[CtxSpy, T, undefined | T], Res>,
  name?: string,
): (anAtom: Atom<T>) => Atom<Res>

Real mapState

If operator isn’t create a new atom and mutate the passed you should use with prefix.

import { Atom, AtomState } from '@reatom/core'

declare function withStateHistory<T extends Atom>(
  length: string,
): (anAtom: T) => T & {
  stateHistoryAtom: Atom<AtomState<T>>

We use here T extends Atom instead of much simpler <T>(length: string): (anAtom: Atom<T>) Atom<T> & {...} to save all additional properties witch added by previous operators.

Real historyAtom



import { action, atom, Atom } from '@reatom/core'

// operator accepts an options by a first argument
// and returns function witch accepts target atom
export const delay =
  <T>(ms: number) =>
  (anAtom: Atom<T>) => {
    // to improve debugability compute name of the new atom
    const name = `${}.delay`
    // hide unnecessary meta by `_` prefix in name
    const update = action<T>(`${name}._update`)
    const updateTimeout = atom(-1, `${name}._updateTimeout`)

    return atom((ctx, prevState?: T) => {
      const state = ctx.spy(anAtom)
      // more about action spying:
      const updates = ctx.spy(update)

      // first call, no need to delay
      if (prevState === undefined) return state

      // update from the schedule below
      if (updates.length) return!.payload

      // do not forget to schedule all side effects!
      ctx.schedule(() => {
        const timeout = setTimeout(() => update(ctx, state), ms)
        updateTimeout(ctx, timeout)

      return prevState
    }, name)

Real debounce