Tutorial: Building a Todo App
In this tutorial, you’ll build a complete Todo application with PyReact Framework.
What You’ll Build
A fully functional Todo app with:
Add new todos
Mark todos as complete
Delete todos
Filter todos (all/active/completed)
Persistent storage (localStorage)
Prerequisites
Python 3.10 or higher
PyReact Framework installed
Basic Python knowledge
Step 1: Create the Project
pyreact-framework create todo-app
cd todo-app
pyreact-framework dev
Step 2: Create TodoItem Component
Create src/components/TodoItem.py:
from pyreact import element, Component
class TodoItem(Component):
def render(self):
todo = self.props['todo']
on_toggle = self.props['onToggle']
on_delete = self.props['onDelete']
return element('li', {
'class': f'todo-item {"completed" if todo["completed"] else ""}'
},
element('input', {
'type': 'checkbox',
'checked': todo['completed'],
'onChange': lambda e: on_toggle(todo['id'])
}),
element('span', {'class': 'todo-text'}, todo['text']),
element('button', {
'class': 'delete-btn',
'onClick': lambda e: on_delete(todo['id'])
}, '×')
)
Step 3: Create TodoList Component
Create src/components/TodoList.py:
from pyreact import element, Component
from .TodoItem import TodoItem
class TodoList(Component):
def render(self):
todos = self.props['todos']
filter_type = self.props.get('filter', 'all')
filtered_todos = todos
if filter_type == 'active':
filtered_todos = [t for t in todos if not t['completed']]
elif filter_type == 'completed':
filtered_todos = [t for t in todos if t['completed']]
return element('ul', {'class': 'todo-list'},
*[element(TodoItem, {
'todo': todo,
'onToggle': self.props['onToggle'],
'onDelete': self.props['onDelete']
}) for todo in filtered_todos]
)
Step 4: Create App Component
Update src/index.py:
from pyreact import element, Component, render
from components.TodoList import TodoList
class App(Component):
def __init__(self, props):
super().__init__(props)
self.state = {
'todos': [],
'input': '',
'filter': 'all'
}
def add_todo(self, event):
if self.state['input'].strip():
new_todo = {
'id': len(self.state['todos']) + 1,
'text': self.state['input'],
'completed': False
}
self.setState({
'todos': self.state['todos'] + [new_todo],
'input': ''
})
def toggle_todo(self, todo_id):
todos = self.state['todos'][:]
for todo in todos:
if todo['id'] == todo_id:
todo['completed'] = not todo['completed']
self.setState({'todos': todos})
def delete_todo(self, todo_id):
todos = [t for t in self.state['todos'] if t['id'] != todo_id]
self.setState({'todos': todos})
def set_filter(self, filter_type):
self.setState({'filter': filter_type})
def render(self):
return element('div', {'class': 'app'},
element('h1', {}, 'Todo App'),
element('div', {'class': 'add-todo'},
element('input', {
'type': 'text',
'value': self.state['input'],
'onInput': lambda e: self.setState({'input': e.target.value}),
'placeholder': 'Add a todo...'
}),
element('button', {'onClick': self.add_todo}, 'Add')
),
element(TodoList, {
'todos': self.state['todos'],
'filter': self.state['filter'],
'onToggle': self.toggle_todo,
'onDelete': self.delete_todo
}),
element('div', {'class': 'filters'},
element('button', {
'class': f"filter-btn {'' if self.state['filter'] == 'all' else ''}",
'onClick': lambda e: self.set_filter('all')
}, 'All'),
element('button', {
'class': f"filter-btn {'' if self.state['filter'] == 'active' else ''}",
'onClick': lambda e: self.set_filter('active')
}, 'Active'),
element('button', {
'class': f"filter-btn {'' if self.state['filter'] == 'completed' else ''}",
'onClick': lambda e: self.set_filter('completed')
}, 'Completed')
)
)
render(element(App, {}), root='root')
Step 5: Add Styles
Create src/styles/main.css:
.app {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.add-todo {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.add-todo input {
flex: 1;
padding: 10px;
font-size: 16px;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
border-bottom: 1px solid #eee;
}
.todo-item.completed .todo-text {
text-decoration: line-through;
opacity: 0.5;
}
.delete-btn {
background: #ff4444;
color: white;
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
cursor: pointer;
}
.filters {
display: flex;
gap: 10px;
margin-top: 20px;
}
.filter-btn.active {
background: #007bff;
color: white;
}
Step 6: Run and Test
Your app is ready! Open http://localhost:3000 and test:
Add a new todo
Mark it as complete
Filter by active/completed
Delete a todo