Documentation Index Fetch the complete documentation index at: https://mintlify.com/reatom/reatom/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Actions are Reatom’s way to encapsulate complex logic, perform side effects, and orchestrate multiple state updates. Unlike atoms which store state, actions represent operations that can be executed with parameters and return values.
Actions:
Accept parameters when called
Can perform side effects (API calls, logging, etc.)
Can update multiple atoms
Return values like regular functions
Track their call history
Have atom-like features (subscribe, extend)
Creating Actions
Basic Usage
Create an action with a function:
import { action , atom } from '@reatom/core'
const count = atom ( 0 , 'count' )
// Simple action
const increment = action (() => {
count . set ( prev => prev + 1 )
}, 'increment' )
// Action with parameters
const add = action (( amount : number ) => {
count . set ( prev => prev + amount )
}, 'add' )
// Call the actions
increment () // count is now 1
add ( 5 ) // count is now 6
Type Signature
interface Action < Params extends any [] = any [], Payload = any > {
// Call the action
( ... params : Params ) : Payload
// Subscribe to action calls
subscribe ( cb ?: ( state : ActionState < Params , Payload >) => any ) : Unsubscribe
// Extension system
extend : Extend < this >
// Internal state tracking calls
__reatom : AtomMeta
}
interface ActionState < Params , Payload >
extends Array <{ params : Params ; payload : Payload }> {}
Actions with Return Values
Actions can return values just like regular functions:
const multiply = action (( a : number , b : number ) => {
return a * b
}, 'multiply' )
const result = multiply ( 5 , 3 ) // -> 15
Async Actions
Actions work seamlessly with async operations:
Basic Async
With Error Handling
import { action , atom } from '@reatom/core'
const userData = atom ( null , 'userData' )
const loading = atom ( false , 'loading' )
const fetchUser = action ( async ( userId : string ) => {
loading . set ( true )
try {
const response = await fetch ( `/api/users/ ${ userId } ` )
const data = await response . json ()
userData . set ( data )
return data
} catch ( error ) {
console . error ( 'Failed to fetch user:' , error )
throw error
} finally {
loading . set ( false )
}
}, 'fetchUser' )
// Use it
await fetchUser ( 'user123' )
Use the wrap utility for abortable async operations within actions. See the Effects documentation for more details.
Subscribing to Actions
Actions track their call history and can be subscribed to:
const logAction = action (( message : string ) => {
console . log ( message )
return message . length
}, 'logAction' )
// Subscribe to action calls
const unsub = logAction . subscribe ( calls => {
console . log ( 'Action called' , calls . length , 'times' )
calls . forEach (({ params , payload }) => {
console . log ( 'Params:' , params , 'Payload:' , payload )
})
})
// Immediately logs: "Action called 0 times"
logAction ( 'Hello' )
// Logs:
// "Hello"
// "Action called 1 times"
// "Params: ['Hello'] Payload: 5"
logAction ( 'World' )
// Logs:
// "World"
// "Action called 2 times"
// "Params: ['Hello'] Payload: 5"
// "Params: ['World'] Payload: 5"
Action call history is automatically cleared after each transaction cycle. This prevents memory leaks.
Orchestrating State Updates
Actions excel at coordinating updates across multiple atoms:
const firstName = atom ( '' , 'firstName' )
const lastName = atom ( '' , 'lastName' )
const email = atom ( '' , 'email' )
const isValid = atom ( false , 'isValid' )
const updateUser = action (( data : {
firstName : string
lastName : string
email : string
}) => {
// Update multiple atoms in one action
firstName . set ( data . firstName )
lastName . set ( data . lastName )
email . set ( data . email )
// Validate
const valid = data . firstName && data . lastName && data . email . includes ( '@' )
isValid . set ( valid )
return valid
}, 'updateUser' )
const isUserValid = updateUser ({
firstName: 'John' ,
lastName: 'Doe' ,
email: 'john@example.com'
}) // -> true
Action Composition
Actions can call other actions:
const count = atom ( 0 , 'count' )
const increment = action (() => {
count . set ( prev => prev + 1 )
}, 'increment' )
const decrement = action (() => {
count . set ( prev => prev - 1 )
}, 'decrement' )
const reset = action (() => {
count . set ( 0 )
}, 'reset' )
// Composed action
const incrementTwice = action (() => {
increment ()
increment ()
}, 'incrementTwice' )
// Complex composition
const resetAndIncrement = action (( amount : number ) => {
reset ()
for ( let i = 0 ; i < amount ; i ++ ) {
increment ()
}
return count ()
}, 'resetAndIncrement' )
Reading Atoms in Actions
Actions can read atom values to make decisions:
const count = atom ( 0 , 'count' )
const maxCount = atom ( 10 , 'maxCount' )
const incrementIfAllowed = action (() => {
const current = count ()
const max = maxCount ()
if ( current < max ) {
count . set ( current + 1 )
return true
}
console . log ( 'Maximum reached!' )
return false
}, 'incrementIfAllowed' )
Action Middleware
Extend actions with middleware to add custom behavior:
import { action , withActionMiddleware } from '@reatom/core'
// Create a logging middleware
const withLogging = withActionMiddleware (( action ) => {
return ( next , ... params ) => {
console . log ( `[ ${ action . name } ] called with:` , params )
const result = next ( ... params )
console . log ( `[ ${ action . name } ] returned:` , result )
return result
}
})
const add = action (( a : number , b : number ) => a + b , 'add' )
. extend ( withLogging )
add ( 2 , 3 )
// Logs: "[add] called with: [2, 3]"
// Logs: "[add] returned: 5"
Common Patterns
const formData = atom ({ email: '' , password: '' }, 'formData' )
const submitting = atom ( false , 'submitting' )
const errors = atom < string []>([], 'errors' )
const submitForm = action ( async () => {
const data = formData ()
// Validation
const validationErrors = []
if ( ! data . email . includes ( '@' )) {
validationErrors . push ( 'Invalid email' )
}
if ( data . password . length < 8 ) {
validationErrors . push ( 'Password too short' )
}
if ( validationErrors . length > 0 ) {
errors . set ( validationErrors )
return false
}
// Submit
submitting . set ( true )
errors . set ([])
try {
await fetch ( '/api/submit' , {
method: 'POST' ,
body: JSON . stringify ( data ),
})
return true
} catch ( err ) {
errors . set ([ 'Submission failed' ])
return false
} finally {
submitting . set ( false )
}
}, 'submitForm' )
Optimistic Updates
const todos = atom ([], 'todos' )
const addTodo = action ( async ( text : string ) => {
// Optimistic update
const tempId = Date . now ()
const tempTodo = { id: tempId , text , synced: false }
todos . set ( prev => [ ... prev , tempTodo ])
try {
// Sync with server
const response = await fetch ( '/api/todos' , {
method: 'POST' ,
body: JSON . stringify ({ text }),
})
const serverTodo = await response . json ()
// Replace temp with real
todos . set ( prev =>
prev . map ( t => t . id === tempId
? { ... serverTodo , synced: true }
: t
)
)
return serverTodo
} catch ( error ) {
// Rollback on failure
todos . set ( prev => prev . filter ( t => t . id !== tempId ))
throw error
}
}, 'addTodo' )
Debounced Search
const searchQuery = atom ( '' , 'searchQuery' )
const searchResults = atom ([], 'searchResults' )
const searching = atom ( false , 'searching' )
let searchTimeout : any
const search = action ( async ( query : string ) => {
searchQuery . set ( query )
// Clear previous timeout
clearTimeout ( searchTimeout )
if ( ! query ) {
searchResults . set ([])
return
}
// Debounce
await new Promise ( resolve => {
searchTimeout = setTimeout ( resolve , 300 )
})
searching . set ( true )
try {
const response = await fetch ( `/api/search?q= ${ query } ` )
const results = await response . json ()
searchResults . set ( results )
} finally {
searching . set ( false )
}
}, 'search' )
Best Practices
Use actions for side effects
Keep side effects in actions, not in computed values: // Good - side effect in action
const saveData = action ( async ( data ) => {
await fetch ( '/api/save' , { method: 'POST' , body: JSON . stringify ( data ) })
})
// Avoid - side effect in computed
const autoSave = computed (() => {
fetch ( '/api/save' , { method: 'POST' }) // Don't do this!
})
Action names should describe what they do: // Good
const fetchUser = action ( ... )
const saveSettings = action ( ... )
const deleteItem = action ( ... )
// Avoid
const user = action ( ... )
const settings = action ( ... )
Return values that indicate success or provide results: // Good - returns success status
const save = action ( async ( data ) => {
try {
await api . save ( data )
return { success: true }
} catch ( error ) {
return { success: false , error }
}
})
Computed Derive state automatically
Effects Run reactive side effects
Extend Customize action behavior