Atoms
Atoms are pieces of state that can be read and written to from any component or hook. In simple cases, they can be used the same way you would use useState
:
import { atom, useAtom } from 'atomic-state'
// The store
const textState = atom({
key: 'text',
default: ''
})
export default function App() {
const [text, setText] = useAtom(textState)
// Reading from and writing to the atom
return (
<div>
<h2>Text: {text}</h2>
<input
value={text}
onChange={(e) => {
setText(e.target.value)
}}
/>
</div>
)
}
Let's create a basic todo app
Todo App
For the todo app to be complete, it needs these basic features:
- A way to add todos
- Change the
completed
status of a todo - Remove todos
- Get the list of completed todos
Creating the app
First, create an atom that will contain the todos:
import { atom } from 'atomic-state'
export const todosState = atom({
key: 'todos',
default: []
})
Next, create a component that adds new todos to our todo list:
import { useState } from 'react'
import { useAtom } from 'atomic-state'
import { todosState } from '@/states'
export default function TodoForm() {
// Using the todosState
const [todos, setTodos] = useAtom(todosState)
const [newTodo, setNewTodo] = useState('')
return (
<div>
<input value={newTodo} onChange={(e) => setNewTodo(e.target.value)} />
<button
onClick={() => {
setTodos((previousTodos) => [
{
title: newTodo,
completed: false,
id: crypto.randomUUID()
},
...previousTodos
])
}}
>
Save
</button>
</div>
)
}
Next, create a component to show the todos:
import { useValue } from 'atomic-state'
import { todosState } from '@/states'
export default function Todos() {
const todos = useValue(todosState)
return (
<section>
{todos.map((todo) => (
<div key={'show-' + todo.id}>
<h2>{todo.title}</h2>
<p>{todo.completed ? 'Completed' : 'Incomplete'}</p>
</div>
))}
</section>
)
}
The useValue
hook returns only the value of the state
Render both components:
import { AtomicState } from 'atomic-state'
import TodoForm from '@/components/TodoForm'
import Todos from '@/components/Todos'
export default function App() {
return (
<main>
<AtomicState>
<TodoForm />
<Todos />
</AtomicState>
</main>
)
}
So far it works, but there should be a way to remove todos or toggle their completed
status.
To do that, add this action
to the todosState
atom:
function modifyTodoAction({ args, dispatch }) {
const { id, type } = args
const todoActions = {
REMOVE: (todos) => todos.filter((todo) => todo.id !== id),
TOGGLE: (todos) =>
todos.map((todo) => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
}
}
return todo
})
}
if (type in todoActions) setTodos(todoActions[type])
}
You don't need to return anything from actions. The state can be set using
dispatch
import { atom } from 'atomic-state'
export const todosState = atom({
key: 'todos',
default: [],
actions: {
modify: modifyTodoAction
}
})
For better static typing, add the action directly in actions
, since all the
necessary typing is inferred, included the state type
And use it in the Todos
component:
import { useAtom } from 'atomic-state'
import { todosState } from '@/states'
export default function Todos() {
const [todos, setTodos, todosActions] = useAtom(todosState)
return (
<section>
{todos.map((todo) => (
<div key={'show-' + todo.id}>
<h2>{todo.title}</h2>
<p>{todo.completed ? 'Completed' : 'Incomplete'}</p>
<div>
<button
onClick={() => {
const willRemoveTodo = confirm('Delete this todo?')
if (willRemoveTodo) {
todosActions.modify({
type: 'REMOVE',
id: todo.id
})
}
}}
>
Remove
</button>
<button
onClick={() => {
todosActions.modify({
type: 'TOGGLE',
id: todo.id
})
}}
>
{todo.completed ? 'Done' : 'Mark as done'}
</button>
</div>
</div>
))}
</section>
)
}