← All reusables
withHistory
extensionInstallation
npx jsrepo add github/reatom/reusables withHistory Copy the source code below and save it to the specified file path in your project.
with-history.ts
import {
computed,
type Atom,
type AtomState,
type Computed,
type Ext,
} from '@reatom/core'
/** Extension interface for withHistory. */
export interface HistoryExt<State> {
/** Computed atom containing current state and past states. */
history: Computed<[State, ...State[]]>
}
/**
* Atom extension that tracks state history with configurable length.
*
* Adds a `.history` computed atom that returns a tuple of `[currentState,
* ...previousStates]`.
*
* @example
* const counter = atom(0, 'counter').extend(withHistory())
* counter.set(1)
* counter.set(2)
* counter.history() // [2, 1, 0] (current + 2 previous)
*
* @param length - Number of previous states to keep (default: 2)
*/
export const withHistory = <Target extends Atom>(
length = 2,
): Ext<Target, HistoryExt<AtomState<Target>>> => {
type State = AtomState<Target>
type History = [State, ...State[]]
return (target) => ({
history: computed(
(state: History | undefined) =>
[target(), ...(state || []).slice(0, length)] satisfies History,
`${target.name}.history`,
),
})
} Documentation
withHistory
Atom extension that tracks state history with configurable length.
withHistory(length?)
Adds a .history computed atom that returns a tuple of [currentState, ...previousStates].
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
length |
number |
2 |
Number of previous states to keep |
Returns
Extension that adds:
| Property | Type | Description |
|---|---|---|
history |
Computed<[current: State, ...past: State[]]> |
Computed with current and past states |
Example
import { atom } from '@reatom/core'
import { withHistory } from '#reatom/extension/with-history'
const counter = atom(0, 'counter').extend(withHistory(3))
counter.set(1)
counter.set(2)
counter.set(3)
counter.history() // [3, 2, 1, 0]
Undo functionality
const counter = atom(0, 'counter').extend(withHistory(10))
// Subscribe to activate the history computed
const unsub = counter.history.subscribe(() => {})
counter.set(1)
counter.set(2)
counter.set(3)
// Undo: restore previous state
const undo = () => {
const [_current, previous] = counter.history()
if (previous !== undefined) {
counter.set(previous)
}
}
undo()
console.log(counter()) // 2
Tracking changes over time
const temperature = atom(20, 'temperature').extend(withHistory(5))
effect(() => {
const [current, ...previous] = temperature.history()
const all = [current, ...previous]
const avg = all.reduce((a, b) => a + b, 0) / all.length
console.log(`Current: ${current}°C, Average: ${avg.toFixed(1)}°C`)
})
temperature.set(22)
temperature.set(19)
temperature.set(23)
Notes
- The history computed must be subscribed to in order to track changes
- History length does not include the current state (e.g.,
length=2keeps current + 2 previous = 3 total) - Works with any state type (primitives, objects, arrays)
- Due to Reatom's batching, call
history()between state changes to ensure each intermediate value is captured
Example
import { atom, effect } from '@reatom/core'
import { withHistory } from './with-history'
// --- Basic counter with undo capability ---
const counter = atom(0, 'counter').extend(withHistory(10))
// Subscribe to activate the computed
const unsub = counter.history.subscribe(() => {})
counter.set(1)
console.log(counter.history()) // [1, 0]
counter.set(2)
console.log(counter.history()) // [2, 1, 0]
counter.set(3)
console.log(counter.history()) // [3, 2, 1, 0]
// Simple undo: restore previous state
const undo = () => {
const [_current, previous] = counter.history()
if (previous !== undefined) {
counter.set(previous)
}
}
undo()
console.log(counter()) // 2
unsub()
// --- Form state with history for undo/redo ---
interface FormState {
username: string
email: string
}
const formState = atom<FormState>(
{ username: '', email: '' },
'formState',
).extend(withHistory(20))
effect(() => {
const history = formState.history()
console.log(`History length: ${history.length}`)
})
formState.set({ username: 'john', email: '' })
formState.set({ username: 'john', email: 'john@example.com' })
// Access previous states for undo functionality
const [current, ...past] = formState.history()
console.log('Current:', current)
console.log('Previous states:', past)
// --- Tracking value changes over time ---
const temperature = atom(20, 'temperature').extend(withHistory(5))
effect(() => {
const [current, ...previous] = temperature.history()
const avg =
[current, ...previous].reduce((a, b) => a + b, 0) / (previous.length + 1)
console.log(`Current: ${current}°C, Average: ${avg.toFixed(1)}°C`)
})
temperature.set(22)
temperature.set(19)
temperature.set(23)
temperature.set(21)