Building a Reactive Todo App with ctrodb and React
Step-by-step guide to building a real-time todo app using useQuery and useMutation
The classic todo app is a good way to see how ctrodb's React hooks work in practice. This post walks through the full implementation.
Setup
npm install ctrodb react react-dom
Create the database with a schema:
import { Database } from "ctrodb"
import { DatabaseProvider } from "ctrodb/react"
const db = new Database({
schema: {
version: 1,
collections: {
todos: {
fields: {
title: { type: "string", required: true },
done: { type: "boolean", default: false },
createdAt: { type: "number", default: () => Date.now() },
},
},
},
},
})
await db.connect()
export function App() {
return (
<DatabaseProvider db={db}>
<TodoApp />
</DatabaseProvider>
)
}
The todo list
useQuery fetches all todos and re-renders when anything changes in the collection:
import { useQuery, useMutation } from "ctrodb/react"
function TodoList() {
const todos = useQuery("todos", (q) =>
q.sort({ createdAt: "desc" })
)
const { update, delete: del } = useMutation("todos")
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<label className={todo.done ? "line-through" : ""}>
<input
type="checkbox"
checked={todo.done}
onChange={() => update(todo.id, { done: !todo.done })}
/>
{todo.title}
</label>
<button onClick={() => del(todo.id)}>x</button>
</li>
))}
</ul>
)
}
Add todo form
function AddTodo() {
const { create, loading } = useMutation("todos")
const [title, setTitle] = useState("")
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
if (!title.trim()) return
await create({ title })
setTitle("")
}
return (
<form onSubmit={handleSubmit}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="What needs to be done?"
/>
<button disabled={loading || !title.trim()}>
{loading ? "Adding..." : "Add"}
</button>
</form>
)
}
How reactivity works
When create is called, ctrodb stores the record and emits a change event. useQuery subscribes to these events and re-fetches the data. The component re-renders with the new list — no manual state management needed.
The same flow works for updates and deletes. Every mutation triggers a re-render of every component querying the same collection.
The complete app
See the full example in the repo for a more complete version with search and relations.
Related posts
Client-Side Full-Text Search with ctrodb
Build a complete search experience in the browser using ctrodb's inverted index engine, tokenizer, and search API.
PluginsExtending ctrodb with Custom Plugins
Leverage ctrodb's plugin system to add custom validation rules, lifecycle hooks, and data transformations.
TransactionsTransactions and Data Integrity in ctrodb
Ensure data consistency with ctrodb's transaction system, rollback support, and comprehensive error types.