# wip task board transaction

Source: docs/source/exhibits/guides/getting-started/wip-task-board-transaction.tsx

```tsx
import { atom, runTransaction, transaction } from "atom.io"
import { useI, useO } from "atom.io/react"
import type { JSX } from "react/jsx-runtime"

const BLOCKED_TASK_IDS = new Set([`billing`])

const todoIdsAtom = atom<string[]>({
	key: `todoIds`,
	default: [`billing`, `docs`],
})

const doneIdsAtom = atom<string[]>({
	key: `doneIds`,
	default: [`setup`],
})

const taskBoardErrorAtom = atom<string | null>({
	key: `taskBoardError`,
	default: null,
})

const markDoneTransaction = transaction<(taskId: string) => void>({
	key: `markDone`,
	do: ({ get, set }, taskId) => {
		set(
			todoIdsAtom,
			get(todoIdsAtom).filter((id) => id !== taskId),
		)

		if (BLOCKED_TASK_IDS.has(taskId)) {
			throw new Error(`Blocked tasks need approval before they can move.`)
		}

		set(doneIdsAtom, [taskId, ...get(doneIdsAtom)])
	},
})

const markDone = runTransaction(markDoneTransaction)

export function TaskBoard(): JSX.Element {
	const todoIds = useO(todoIdsAtom)
	const doneIds = useO(doneIdsAtom)
	const error = useO(taskBoardErrorAtom)
	const setError = useI(taskBoardErrorAtom)

	return (
		<section>
			{error ? <p role="alert">{error}</p> : null}

			<h2>To do</h2>
			<ul>
				{todoIds.map((taskId) => (
					<li key={taskId}>
						{taskId}
						<button
							type="button"
							onClick={() => {
								try {
									setError(null)
									markDone(taskId)
								} catch (thrown) {
									setError(
										thrown instanceof Error
											? thrown.message
											: `Could not move task.`,
									)
								}
							}}
						>
							Mark done
						</button>
					</li>
				))}
			</ul>

			<h2>Done</h2>
			<ul>
				{doneIds.map((taskId) => (
					<li key={taskId}>{taskId}</li>
				))}
			</ul>
		</section>
	)
}
```
