Docs
Atoms

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:

@/states/index.js
import { atom } from 'atomic-state'
 
export const todosState = atom({
  key: 'todos',
  default: []
})

Next, create a component that adds new todos to our todo list:

@/components/TodoForm.jsx
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:

@/components/Todos.jsx
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:

src/App.jsx
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

states/index.js
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:

components/Todos.jsx
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>
  )
}
Last updated on January 27, 2024