Docs
Atoms

Atoms

Atoms are pieces of state that can be read and written to from any component or hook:

import { create } from 'atomic-state'
 
// The store
const useText = create({
  key: 'text',
  default: ''
})
 
export default function App() {
  const [text, setText] = useText()
 
  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 { create } from 'atomic-state'
 
const todos = create({
  key: 'todos',
  default: []
})
 
export const useTodos = todos.useValue
export const setTodos = todos.setValue

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

@/components/TodoForm.jsx
import { useState } from 'react'
 
import { setTodos } from '@/states'
 
export default function TodoForm() {
  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
          ])
          setNewTodo('')
        }}
      >
        Save
      </button>
    </div>
  )
}

Next, create a component to show the todos:

@/components/Todos.jsx
import { useTodos } from '@/states'
 
export default function Todos() {
  const todos = useTodos()
 
  return (
    <section>
      {todos.map((todo) => (
        <div key={'show-' + todo.id}>
          <h2>{todo.title}</h2>
          <p>{todo.completed ? 'Completed' : 'Incomplete'}</p>
        </div>
      ))}
    </section>
  )
}

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 todos atom:

You don't need to return anything from actions.
states/index.js
import { create } from 'atomic-state'
 
type TodoType = {
  id: string
  title: string
  completed: boolean
}
 
function removeTodo({ args }: { args: TodoType }) {
  setTodos(previousTodos => previousTodos.filter(todo => todo.id !== args.id))
}
 
function toggleTodoCompleted({ args }: { args: TodoType }) {
  setTodos(previousTodos =>
    previousTodos.map(todo => {
      if (todo.id === args.id) {
        return {
          ...todo,
          completed: !todo.completed
        }
      }
      return todo
    })
  )
}
 
const todos = create({
  key: 'todos',
  default: [] as TodoType[],
  actions: {
    removeTodo,
    toggleTodoCompleted
  }
})
 
export const useTodos = todos.useValue
export const setTodos = todos.setValue
export const todosActions = todos.actions
 

For better static typing, add actions in actions. All the necessary typing is inferred, including the state type

And use it in the Todos component:

components/Todos.jsx
import { useTodos, todosActions } from '@/state'
 
export default function Todos() {
  const todos = useTodos()
 
  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.removeTodo(todo)
                }
              }}
            >
              Remove
            </button>
            <button
              onClick={() => {
                todosActions.toggleTodoCompleted(todo)
              }}
            >
              {todo.completed ? 'Done' : 'Mark as done'}
            </button>
          </div>
        </div>
      ))}
    </section>
  )
}
Last updated on August 8, 2024