Bỏ qua để đến nội dung

🪝 Hooks

Học cách sử dụng Hooks để quản lý state local trong components. ⏱️ 15 phút

Nắm vững:

  • ft.use_state() - State local trong component
  • Khi nào dùng hooks vs observables

use_state() tạo state local cho component, tự động re-render khi state thay đổi:

import flet as ft
@ft.component
def Counter():
# Khai báo state với giá trị khởi tạo
count, set_count = ft.use_state(0)
return ft.Row([
ft.IconButton(ft.Icons.REMOVE, on_click=lambda _: set_count(count - 1)),
ft.Text(str(count), size=24),
ft.IconButton(ft.Icons.ADD, on_click=lambda _: set_count(count + 1)),
], alignment=ft.MainAxisAlignment.CENTER)
def main(page: ft.Page):
page.add(Counter())
ft.run(main)
count, set_count = ft.use_state(0)
# │ │ │
# │ │ └── Giá trị khởi tạo
# │ └── Hàm để cập nhật state
# └── Giá trị hiện tại của state
@ft.component
def NameInput():
name, set_name = ft.use_state("")
return ft.Column([
ft.TextField(
value=name,
label="Tên của bạn",
on_change=lambda e: set_name(e.control.value),
),
ft.Text(f"Xin chào, {name}!" if name else "Nhập tên của bạn"),
])
@ft.component
def ToggleCard():
is_visible, set_visible = ft.use_state(True)
return ft.Column([
ft.Switch(
label="Hiển thị nội dung",
value=is_visible,
on_change=lambda _: set_visible(not is_visible),
),
ft.Text("Nội dung secret! 🤫") if is_visible else ft.Container(),
])
@ft.component
def TagList():
tags, set_tags = ft.use_state([])
tag_input, set_input = ft.use_state("")
def add_tag(_):
if tag_input and tag_input not in tags:
set_tags([*tags, tag_input]) # Tạo list mới
set_input("")
def remove_tag(tag):
set_tags([t for t in tags if t != tag])
return ft.Column([
ft.Row([
ft.TextField(
value=tag_input,
hint_text="Thêm tag...",
on_change=lambda e: set_input(e.control.value),
on_submit=add_tag,
),
ft.IconButton(ft.Icons.ADD, on_click=add_tag),
]),
ft.Row([
ft.Chip(
label=ft.Text(tag),
on_delete=lambda _, t=tag: remove_tag(t),
)
for tag in tags
], wrap=True),
])
@ft.component
def UserForm():
user, set_user = ft.use_state({"name": "", "email": ""})
def update_field(field, value):
set_user({**user, field: value}) # Spread operator để tạo object mới
return ft.Column([
ft.TextField(
label="Tên",
value=user["name"],
on_change=lambda e: update_field("name", e.control.value),
),
ft.TextField(
label="Email",
value=user["email"],
on_change=lambda e: update_field("email", e.control.value),
),
ft.Text(f"User: {user}"),
])

[!CAUTION] Không mutate state trực tiếp! Luôn tạo giá trị mới:

# ❌ SAI - Mutate trực tiếp
items.append(new_item)
set_items(items)
# ✅ ĐÚNG - Tạo list mới
set_items([*items, new_item])
@ft.component
def LoginForm():
# Nhiều states độc lập
username, set_username = ft.use_state("")
password, set_password = ft.use_state("")
is_loading, set_loading = ft.use_state(False)
error, set_error = ft.use_state("")
def handle_login(_):
set_loading(True)
set_error("")
# ... login logic
return ft.Column([
ft.TextField(label="Username", value=username,
on_change=lambda e: set_username(e.control.value)),
ft.TextField(label="Password", value=password, password=True,
on_change=lambda e: set_password(e.control.value)),
ft.Text(error, color=ft.Colors.RED) if error else ft.Container(),
ft.FilledButton(
"Login",
on_click=handle_login,
disabled=is_loading,
),
])
Đặc điểm use_state @ft.observable
Phạm vi Component local Toàn app
Chia sẻ Không
Định nghĩa Trong component Ngoài component
Use case Form input, toggle, UI state User data, cart, settings
flowchart LR
subgraph "Component A"
S1["use_state"]
end
subgraph "Component B"
S2["use_state"]
end
subgraph "Toàn App"
O["@observable<br/>AppState"]
end
O --> A
O --> B
style S1 fill:#4CAF50,color:white
style S2 fill:#4CAF50,color:white
style O fill:#2196F3,color:white
import flet as ft
@ft.component
def CounterWithReset():
count, set_count = ft.use_state(0)
return ft.Card(
content=ft.Container(
content=ft.Column([
ft.Text("Counter", size=20, weight=ft.FontWeight.BOLD),
ft.Text(str(count), size=48),
ft.Row([
ft.IconButton(ft.Icons.REMOVE_CIRCLE, on_click=lambda _: set_count(count - 1)),
ft.IconButton(ft.Icons.ADD_CIRCLE, on_click=lambda _: set_count(count + 1)),
ft.IconButton(ft.Icons.REFRESH, on_click=lambda _: set_count(0)),
], alignment=ft.MainAxisAlignment.CENTER),
], horizontal_alignment=ft.CrossAxisAlignment.CENTER),
padding=20,
),
)
def main(page: ft.Page):
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.add(CounterWithReset())
ft.run(main)

Đã nắm vững Hooks! Hãy ứng dụng vào Mini Project: Counter App để thực hành.