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