Skip to content

Actions

Action is a base Reatom primitive that increases the quality of your code in many ways: organization and readability, debugability, extensibility.

The beauty of Reatom is that you don’t need to use actions for simple updates, like (value) => myAtom.set(value). Actions are useful for complex operations, like data mappings, API calls and other side effects.

You can call actions anywhere just like regular functions. You can describe its parameters just like with regular functions. You can type your action function with generics as usual. action itself is a simple decorator which adds some extra features to your function, but does not limit you in any way.

import { atom, action } from '@reatom/core'
export const list = atom([])
const isListLoading = atom(false)
const loadList = action(async (page: number) => {
isListLoading.set(true)
try {
const response = await fetch(`/api/list?page=${page}`)
const payload = await response.json()
list.set(payload)
} finally {
isListLoading.set(false)
}
})
loadList(1) // Promise

Most Reatom units accept an optional name for debugging purposes. We highly recommend using it, as it helps to debug the runtime dataflow.

export const list = atom([], 'list')
const isListLoading = atom(false, 'isListLoading')
const loadList = action(async (page: number) => {
// ...
}, 'loadList')

That’s better!

However, each atom has a .actions method to bind and name related actions.

It accepts a callback for creating related methods, it is called immediately and returns the original atom. The callback accepts the processed target as an argument and should return an object with related methods.

import { atom, action } from '@reatom/core'
export const list = atom([], 'list').actions(
(target) => ({
async load(page: number) {
isListLoading.set(true)
try {
const response = await fetch(`/api/list?page=${page}`)
const payload = await response.json()
list.set(payload)
target.set(payload)
} finally {
isListLoading.set(false)
}
},
}),
)
const isListLoading = atom(false, 'isListLoading')
list.load(1) // returns Promise

.actions converts passed methods to actions with relevant names. The code becomes more elegant and better organized!

But what if we want to proceed with this domain code organization and combine all things together? We have the .extend method for that!

import { atom, action } from '@reatom/core'
export const list = atom([], 'list')
.extend((target) => ({
// describe things that you want to assign to the current atom
isLoading: atom(false, `${target.name}.isLoading`),
}))
.actions((target) => ({
async load(page: number) {
target.isLoading.set(true)
const response = await fetch(`/api/list?page=${page}`)
const payload = await response.json()
target.set(payload)
target.isLoading.set(false)
},
}))

Now you can access your states in a clean and readable way:

src/component/Paging.tsx
import React from 'react'
import { reatomComponent } from '@reatom/react'
import { list } from './model'
const Paging = reatomComponent(() => {
const [page, setPage] = React.useState(1)
React.useEffect(() => {
list.load(page)
}, [page])
const isLoading = list.isLoading()
return (
<button onClick={() => setPage((page) => page + 1)} disabled={isLoading}>
{isLoading ? 'Loading...' : 'Next page'}
</button>
)
})
const List = reatomComponent(() => (
<section>
<Paging />
{list().map(/* ... */)}
</section>
))

Awesome, now you can couple relative states with relative components without a props drilling!

But this is just the beginning, .extend can give us much more! Check out the next section to learn more about it.