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
Effects are reactive side effects that automatically track their dependencies and clean themselves up when dependencies change or when the effect is aborted. They’re designed for running side effects in response to state changes.
Effects:
Automatically track atom dependencies
Re-run when dependencies change
Support async operations with abort handling
Clean up automatically on context abort
Return an unsubscribe function for manual cleanup
Creating Effects
Basic Usage
import { atom , effect } from '@reatom/core'
const count = atom ( 0 , 'count' )
// Create an effect that runs when count changes
effect (() => {
console . log ( 'Count is:' , count ())
}, 'logCount' )
// Immediately logs: "Count is: 0"
count . set ( 5 )
// Logs: "Count is: 5"
Type Signature
interface Effect < State > extends Computed < State > {
unsubscribe : Unsubscribe
}
function effect < T >(
cb : () => T ,
name ?: string
) : Effect < T >
Automatic Dependency Tracking
Effects automatically track any atoms read during execution:
const firstName = atom ( 'John' , 'firstName' )
const lastName = atom ( 'Doe' , 'lastName' )
// Automatically depends on both firstName and lastName
effect (() => {
console . log ( `Full name: ${ firstName () } ${ lastName () } ` )
}, 'logFullName' )
// Logs: "Full name: John Doe"
firstName . set ( 'Jane' )
// Logs: "Full name: Jane Doe"
lastName . set ( 'Smith' )
// Logs: "Full name: Jane Smith"
Effects automatically subscribe and start running immediately upon creation.
Async Effects
Effects work seamlessly with async operations:
import { atom , effect , wrap } from '@reatom/core'
const userId = atom ( 'user123' , 'userId' )
const userData = atom ( null , 'userData' )
// Fetch user data whenever userId changes
effect ( async () => {
const id = userId ()
console . log ( 'Fetching user:' , id )
try {
const response = await wrap ( fetch ( `/api/users/ ${ id } ` ))
const data = await wrap ( response . json ())
userData . set ( data )
} catch ( error ) {
if ( ! isAbort ( error )) {
console . error ( 'Failed to fetch user:' , error )
}
}
}, 'fetchUserEffect' )
// When userId changes, previous fetch is aborted
userId . set ( 'user456' )
Use wrap() with promises to make them abortable. When the effect re-runs or is cancelled, wrapped promises are automatically aborted.
Polling Pattern
Effects are perfect for polling data:
import { atom , effect , wrap , sleep } from '@reatom/core'
const isPollingActive = atom ( true , 'isPollingActive' )
const data = atom ( 0 , 'data' )
effect ( async () => {
if ( ! isPollingActive ()) return
console . log ( 'Starting polling...' )
while ( true ) {
try {
// Fetch data
const response = await wrap ( fetch ( '/api/data' ))
const newData = await wrap ( response . json ())
data . set ( newData . value )
// Wait 5 seconds
await wrap ( sleep ( 5000 ))
} catch ( error ) {
if ( isAbort ( error )) {
console . log ( 'Polling stopped' )
break
}
throw error
}
}
}, 'pollingEffect' )
// Stop polling by changing the atom
isPollingActive . set ( false )
Conditional Effects
Effects can run conditionally based on state:
Guard Clause
Conditional Logic
const isEnabled = atom ( true , 'isEnabled' )
const value = atom ( 0 , 'value' )
effect (() => {
// Early return if disabled
if ( ! isEnabled ()) return
console . log ( 'Value is:' , value ())
}, 'conditionalEffect' )
Manual Cleanup
Effects return an unsubscribe function for manual cleanup:
const count = atom ( 0 , 'count' )
const counterEffect = effect (() => {
console . log ( 'Count:' , count ())
}, 'counterEffect' )
// Effect is running...
count . set ( 1 ) // Logs: "Count: 1"
count . set ( 2 ) // Logs: "Count: 2"
// Stop the effect
counterEffect . unsubscribe ()
count . set ( 3 ) // No log - effect is stopped
Effect Lifecycle
Effects automatically handle cleanup when context is aborted:
import { atom , effect , wrap , context } from '@reatom/core'
const active = atom ( true , 'active' )
// Create a new context
const ctx = context . start ()
ctx . run (() => {
effect ( async () => {
if ( ! active ()) return
console . log ( 'Effect started' )
try {
while ( true ) {
await wrap ( sleep ( 1000 ))
console . log ( 'Tick' )
}
} catch ( error ) {
if ( isAbort ( error )) {
console . log ( 'Effect cleaned up' )
}
}
}, 'tickEffect' )
})
// Reset context - automatically cleans up all effects
context . reset ()
// Logs: "Effect cleaned up"
Common Patterns
Local Storage Sync
const settings = atom (
JSON . parse ( localStorage . getItem ( 'settings' ) || '{}' ),
'settings'
)
// Sync to localStorage on changes
effect (() => {
const value = settings ()
localStorage . setItem ( 'settings' , JSON . stringify ( value ))
}, 'syncSettings' )
Document Title
const pageTitle = atom ( 'Home' , 'pageTitle' )
const unreadCount = atom ( 0 , 'unreadCount' )
effect (() => {
const title = pageTitle ()
const unread = unreadCount ()
document . title = unread > 0
? `( ${ unread } ) ${ title } `
: title
}, 'updateDocTitle' )
WebSocket Connection
const isConnected = atom ( false , 'isConnected' )
const messages = atom < string []>([], 'messages' )
effect (() => {
if ( ! isConnected ()) return
const ws = new WebSocket ( 'wss://example.com' )
ws . onmessage = ( event ) => {
messages . set ( prev => [ ... prev , event . data ])
}
ws . onopen = () => {
console . log ( 'Connected' )
}
// Cleanup on disconnect
return () => {
ws . close ()
console . log ( 'Disconnected' )
}
}, 'websocketEffect' )
const currentRoute = atom ( '/home' , 'currentRoute' )
effect (() => {
// Scroll to top whenever route changes
const route = currentRoute ()
window . scrollTo ( 0 , 0 )
console . log ( 'Navigated to:' , route )
}, 'scrollToTop' )
Auto-save
import { atom , effect , wrap , sleep } from '@reatom/core'
const content = atom ( '' , 'content' )
const lastSaved = atom < Date | null >( null , 'lastSaved' )
effect ( async () => {
const text = content ()
// Don't save empty content
if ( ! text ) return
// Debounce - wait 2 seconds
await wrap ( sleep ( 2000 ))
// Save
console . log ( 'Auto-saving...' )
await wrap ( fetch ( '/api/save' , {
method: 'POST' ,
body: JSON . stringify ({ content: text })
}))
lastSaved . set ( new Date ())
console . log ( 'Saved at:' , lastSaved ())
}, 'autoSave' )
Effects vs Actions
Feature Effect Action Auto-subscribes Yes No Accepts parameters No Yes Auto-tracks dependencies Yes No Returns value No (returns subscription) Yes Use case Reactive side effects Imperative operations
Effects vs Computed
Feature Effect Computed Tracks dependencies Yes Yes Can have side effects Yes No (should be pure) Return value used No Yes Runs immediately Yes Only when read Use case Side effects Derived state
Abort Handling
Effects integrate with Reatom’s abort system:
import { atom , effect , wrap , isAbort } from '@reatom/core'
const query = atom ( '' , 'query' )
const results = atom ([], 'results' )
effect ( async () => {
const q = query ()
if ( ! q ) {
results . set ([])
return
}
try {
// This will be aborted if query changes
const response = await wrap ( fetch ( `/api/search?q= ${ q } ` ))
const data = await wrap ( response . json ())
results . set ( data )
} catch ( error ) {
// Check if it's an abort (expected) or real error
if ( isAbort ( error )) {
console . log ( 'Search aborted (query changed)' )
} else {
console . error ( 'Search failed:' , error )
}
}
}, 'searchEffect' )
// Typing triggers multiple searches, but only the last one completes
query . set ( 'r' )
query . set ( 're' )
query . set ( 'rea' )
query . set ( 'reat' )
query . set ( 'reato' )
query . set ( 'reatom' ) // Only this search completes
Best Practices
Use wrap() for abortable async operations
Always wrap promises to enable automatic cancellation: // Good - abortable
effect ( async () => {
const response = await wrap ( fetch ( '/api/data' ))
})
// Avoid - not abortable
effect ( async () => {
const response = await fetch ( '/api/data' )
})
Handle abort errors gracefully
Check for abort errors to distinguish from real errors: effect ( async () => {
try {
await wrap ( someAsyncOperation ())
} catch ( error ) {
if ( isAbort ( error )) {
// Expected - dependency changed
return
}
// Real error - handle it
handleError ( error )
}
})
Use guard clauses for conditional effects
Early returns make conditional logic clearer: // Good
effect (() => {
if ( ! isEnabled ()) return
doSomething ()
})
// Less clear
effect (() => {
if ( isEnabled ()) {
doSomething ()
}
})
Computed Derive state without side effects
Actions Imperative operations with parameters
Extend Customize effect behavior