Skip to main content

Todo

Manage Home Assistant todo lists with add, remove, update, and toggle operations.

Quick Example

// Component approach
<Todo entityId="todo.shopping_list">
{({ items, itemCount, addItem, toggleItem }) => (
<div>
<h3>Shopping List ({itemCount} items)</h3>
<button onClick={() => addItem('Milk')}>
Add Milk
</button>
{items.map(item => (
<div key={item.uid}>
<span>{item.summary}</span>
<span> - {item.status === 'completed' ? 'DONE' : 'PENDING'}</span>
<button onClick={() => toggleItem(item.uid)}>
Toggle
</button>
</div>
))}
</div>
)}
</Todo>

// Hook approach
const shoppingList = useTodo('todo.shopping_list')
<div>
<h3>Shopping List ({shoppingList.itemCount} items)</h3>
<button onClick={() => shoppingList.addItem('Milk')}>
Add Milk
</button>
{shoppingList.items.map(item => (
<div key={item.uid}>
<span>{item.summary}</span>
<span> - {item.status === 'completed' ? 'DONE' : 'PENDING'}</span>
<button onClick={() => shoppingList.toggleItem(item.uid)}>
Toggle
</button>
</div>
))}
</div>

Component API

Basic Usage

import { Todo } from 'hass-react'

<Todo entityId="todo.shopping_list">
{(todoProps) => (
// Your UI here
)}
</Todo>

Render Props

The Todo component provides these props to your render function:

State Properties

  • itemCount (number) - Total number of items in the list
  • items (TodoItem[]) - Array of todo items
  • isLoadingItems (boolean | undefined) - Whether items are being loaded

Support Properties

  • supportsAddItem (boolean) - List supports adding new items
  • supportsRemoveItem (boolean) - List supports removing items
  • supportsUpdateItem (boolean) - List supports updating item status
  • supportsClearCompleted (boolean) - List supports bulk clearing completed items

Control Methods

  • addItem(summary: string) - Add a new item to the list
  • removeItem(uid: string) - Remove an item from the list
  • updateItem(uid: string, status: 'needs_action' | 'completed') - Update item status
  • toggleItem(uid: string) - Toggle item between completed and needs action
  • clearCompleted() - Remove all completed items

Todo Item Structure

interface TodoItem {
uid: string // Unique identifier
summary: string // Item description
status: 'needs_action' | 'completed' // Current status
due?: string // Due date (optional)
description?: string // Extended description (optional)
}

Entity Properties

  • entityId (string) - The entity ID
  • state (string) - Raw state value from Home Assistant
  • attributes (object) - All entity attributes
  • lastChanged (Date) - When the entity last changed
  • lastUpdated (Date) - When the entity was last updated

Hook API

Basic Usage

import { useTodo } from 'hass-react'

function MyComponent() {
const todoList = useTodo('todo.shopping_list')

// All the same properties as component render props
return <div>{todoList.itemCount} items</div>
}

The useTodo hook returns an object with all the same properties and methods as the component's render props.

List All Todo Lists

Use the useTodos hook to retrieve all available todo list entities:

import { useTodos } from 'hass-react'

function TodoListList() {
const todos = useTodos()

return (
<div>
<h2>Available Todo Lists ({todos.length})</h2>
{todos.map(todo => (
<div key={todo.entity_id}>
{todo.attributes.friendly_name || todo.entity_id}
</div>
))}
</div>
)
}

The useTodos hook fetches all todo list entities from Home Assistant and returns an array of todo list objects.

Examples

Simple Todo List

<Todo entityId="todo.tasks">
{({ items, itemCount, addItem, toggleItem, removeItem, attributes }) => (
<div>
<h3>{attributes.friendly_name} ({itemCount} items)</h3>

<div>
<button onClick={() => addItem('New task')}>
Add Task
</button>
</div>

{items.map(item => (
<div key={item.uid}>
<span style={{
textDecoration: item.status === 'completed' ? 'line-through' : 'none'
}}>
{item.summary}
</span>

<button onClick={() => toggleItem(item.uid)}>
{item.status === 'completed' ? 'Undo' : 'Complete'}
</button>

<button onClick={() => removeItem(item.uid)}>
Remove
</button>
</div>
))}

{items.length === 0 && <p>No items in list</p>}
</div>
)}
</Todo>

Shopping List with Add Form

<Todo entityId="todo.shopping_list">
{({ items, addItem, toggleItem, removeItem, attributes }) => {
const [newItem, setNewItem] = useState('')

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (newItem.trim()) {
addItem(newItem.trim())
setNewItem('')
}
}

return (
<div>
<h3>{attributes.friendly_name}</h3>

<form onSubmit={handleSubmit}>
<input
type="text"
value={newItem}
onChange={(e) => setNewItem(e.target.value)}
placeholder="Add item to shopping list"
/>
<button type="submit">Add</button>
</form>

<div>
{items
.filter(item => item.status === 'needs_action')
.map(item => (
<div key={item.uid}>
<span>{item.summary}</span>
<button onClick={() => toggleItem(item.uid)}>
Mark as Bought
</button>
<button onClick={() => removeItem(item.uid)}>
Remove
</button>
</div>
))}
</div>

<h4>Completed</h4>
<div>
{items
.filter(item => item.status === 'completed')
.map(item => (
<div key={item.uid}>
<span style={{ textDecoration: 'line-through' }}>
{item.summary}
</span>
<button onClick={() => toggleItem(item.uid)}>
Uncheck
</button>
<button onClick={() => removeItem(item.uid)}>
Remove
</button>
</div>
))}
</div>
</div>
)
}}
</Todo>

Todo List with Categories

<Todo entityId="todo.daily_tasks">
{({ items, addItem, toggleItem, clearCompleted, supportsClearCompleted, attributes }) => {
const pendingItems = items.filter(item => item.status === 'needs_action')
const completedItems = items.filter(item => item.status === 'completed')

return (
<div>
<h3>{attributes.friendly_name}</h3>

<div>
<strong>Pending Tasks ({pendingItems.length})</strong>
{pendingItems.map(item => (
<div key={item.uid}>
<span>{item.summary}</span>
{item.due && <span> - Due: {new Date(item.due).toLocaleDateString()}</span>}
<button onClick={() => toggleItem(item.uid)}>
Complete
</button>
</div>
))}
{pendingItems.length === 0 && <p>All tasks completed!</p>}
</div>

<div>
<strong>Completed Tasks ({completedItems.length})</strong>
{supportsClearCompleted && completedItems.length > 0 && (
<button onClick={clearCompleted}>
Clear All Completed
</button>
)}
{completedItems.map(item => (
<div key={item.uid}>
<span style={{ color: 'gray' }}>{item.summary}</span>
<button onClick={() => toggleItem(item.uid)}>
Undo
</button>
</div>
))}
</div>

<button onClick={() => addItem('New task')}>
Add Task
</button>
</div>
)
}}
</Todo>

Multiple Todo Lists

function TodoDashboard() {
const todoLists = [
'todo.shopping_list',
'todo.work_tasks',
'todo.household_chores'
]

return (
<div>
<h2>Todo Dashboard</h2>
{todoLists.map(entityId => (
<Todo key={entityId} entityId={entityId}>
{({ items, itemCount, addItem, attributes }) => {
const pendingCount = items.filter(item => item.status === 'needs_action').length
const completedCount = items.filter(item => item.status === 'completed').length

return (
<div>
<h3>{attributes.friendly_name}</h3>
<p>Pending: {pendingCount}, Completed: {completedCount}</p>
<button onClick={() => addItem('New item')}>
Quick Add
</button>
</div>
)
}}
</Todo>
))}
</div>
)
}

Todo List with Progress

<Todo entityId="todo.project_tasks">
{({ items, itemCount, toggleItem, attributes }) => {
const completedCount = items.filter(item => item.status === 'completed').length
const progressPercent = itemCount > 0 ? Math.round((completedCount / itemCount) * 100) : 0

return (
<div>
<h3>{attributes.friendly_name}</h3>

<div>
Progress: {completedCount} / {itemCount} ({progressPercent}%)
</div>

<div>
{items.map(item => (
<div key={item.uid}>
<label>
<input
type="checkbox"
checked={item.status === 'completed'}
onChange={() => toggleItem(item.uid)}
/>
{item.summary}
</label>
{item.description && <p>{item.description}</p>}
</div>
))}
</div>
</div>
)
}}
</Todo>

Using Hooks

import { useTodo } from 'hass-react'

function TodoCard({ entityId }) {
const todo = useTodo(entityId)
const [newItemText, setNewItemText] = useState('')

const handleAddItem = () => {
if (newItemText.trim()) {
todo.addItem(newItemText.trim())
setNewItemText('')
}
}

const getStatusSummary = () => {
const pending = todo.items.filter(item => item.status === 'needs_action').length
const completed = todo.items.filter(item => item.status === 'completed').length
return { pending, completed }
}

const { pending, completed } = getStatusSummary()

return (
<div>
<h3>{todo.attributes.friendly_name}</h3>

<div>
Total: {todo.itemCount} | Pending: {pending} | Completed: {completed}
</div>

{todo.supportsAddItem && (
<div>
<input
type="text"
value={newItemText}
onChange={(e) => setNewItemText(e.target.value)}
placeholder="New item"
/>
<button onClick={handleAddItem}>
Add
</button>
</div>
)}

<div>
{todo.items.map(item => (
<div key={item.uid}>
<span style={{
textDecoration: item.status === 'completed' ? 'line-through' : 'none',
color: item.status === 'completed' ? 'gray' : 'black'
}}>
{item.summary}
</span>

{todo.supportsUpdateItem && (
<button onClick={() => todo.toggleItem(item.uid)}>
{item.status === 'completed' ? 'Undo' : 'Done'}
</button>
)}

{todo.supportsRemoveItem && (
<button onClick={() => todo.removeItem(item.uid)}>
Delete
</button>
)}
</div>
))}
</div>

{todo.supportsClearCompleted && completed > 0 && (
<button onClick={todo.clearCompleted}>
Clear {completed} Completed Items
</button>
)}

<p>Last updated: {todo.lastUpdated.toLocaleTimeString()}</p>
</div>
)
}