Notr Logo

Examples

Practical examples and patterns.

Todo App

Schema and CRUD with optimistic UI.

'use server'

import { revalidatePath } from 'next/cache'
import { createFn, readFn, updateFn, destroyFn } from '@remcostoeten/drizzleasy'

type TTodo = {
  id: string
  title: string
  description?: string
  completed: boolean
  priority: 'low' | 'medium' | 'high'
  createdAt?: Date
  updatedAt?: Date
}

const create = createFn<TTodo>()
const read = readFn<TTodo>()
const update = updateFn<TTodo>()
const destroy = destroyFn<TTodo>()

export async function createTodo(data: { title: string; description?: string }) {
  const result = await create('todos')({ id: crypto.randomUUID(), title: data.title, description: data.description, completed: false, priority: 'medium' })
  if (!result.error) revalidatePath('/todos')
  return result
}

export async function listTodos() {
  return read('todos')()
}

export async function toggleTodo(id: string, completed: boolean) {
  const result = await update('todos')(id, { completed })
  if (!result.error) revalidatePath('/todos')
  return result
}

export async function deleteTodo(id: string) {
  const result = await destroy('todos')(id)
  if (!result.error) revalidatePath('/todos')
  return result
}
'use client'

import { useOptimisticCrud } from '@remcostoeten/drizzleasy'
import { createTodo, toggleTodo, deleteTodo } from '@/server/actions/todos'

type TTodo = { id: string; title: string; completed: boolean }

type TProps = { initialTodos: TTodo[] }

export function TodoList(props: TProps) {
  const { data, isPending, optimisticCreate } = useOptimisticCrud<TTodo>(props.initialTodos)

  function handleCreate(formData: FormData) {
    const title = String(formData.get('title') || '')
    optimisticCreate({ title, completed: false }, function onCreate() {
      return createTodo({ title })
    })
  }

  return (
    <div>
      <form action={handleCreate}>
        <input name="title" placeholder="Todo title" />
        <button disabled={isPending}>{isPending ? 'Adding…' : 'Add'}</button>
      </form>

      <ul>
        {data.map(function render(todo) {
          return (
            <li key={todo.id}>
              <input type="checkbox" defaultChecked={todo.completed} onChange={function onChange(e) { void toggleTodo(todo.id, e.currentTarget.checked) }} />
              <span>{todo.title}</span>
              <button onClick={function onClick() { void deleteTodo(todo.id) }}>Delete</button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

Batch operations

import { createFn } from '@remcostoeten/drizzleasy'

type TUser = { id: string; email: string; name: string }

async function createMany(users: Array<{ email: string; name: string }>) {
  const create = createFn<TUser>()
  const results = await Promise.all(users.map(function run(u) { return create('users')({ id: crypto.randomUUID(), ...u }) }))
  return results
}