Nguyễn Đình Cường
0 comment
09 Nov, 2026
Sau khi làm Todo bằng JavaScript thuần, bạn sẽ thấy React giúp quản lý giao diện gọn gàng hơn thế nào: bạn mô tả giao diện theo dữ liệu, còn việc cập nhật DOM để React lo. Bài này hướng dẫn bạn dựng Todo bằng React với hooks, chia thành các component nhỏ.
Cần Node.js đã cài. Vite là công cụ tạo dự án nhanh và nhẹ:
npm create vite@latest react-todo -- --template react
cd react-todo
npm install
npm run devMở địa chỉ hiện trong terminal (thường là http://localhost:5173). Trong thư mục src, ta sẽ sửa App.jsx và tạo thêm vài component. Có thể xóa nội dung mẫu trong App.css và index.css nếu muốn giao diện sạch.
Ba ý chính. Một, giao diện là hàm của dữ liệu: cùng dữ liệu thì cùng giao diện. Hai, dữ liệu thay đổi theo thời gian được giữ trong state; khi state đổi, component được vẽ lại. Ba, dữ liệu chảy một chiều từ component cha xuống con qua props, còn con báo ngược lên cha bằng cách gọi hàm được truyền xuống.
import { useState, useEffect } from 'react';
import TodoForm from './TodoForm';
import TodoList from './TodoList';
function loadTodos() {
try {
return JSON.parse(localStorage.getItem('todos')) || [];
} catch {
return [];
}
}
export default function App() {
const [todos, setTodos] = useState(loadTodos);
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
function addTodo(text) {
setTodos((prev) => [...prev, { id: Date.now(), text, done: false }]);
}
function toggleTodo(id) {
setTodos((prev) =>
prev.map((t) => (t.id === id ? { ...t, done: !t.done } : t))
);
}
function removeTodo(id) {
setTodos((prev) => prev.filter((t) => t.id !== id));
}
const remaining = todos.filter((t) => !t.done).length;
return (
<main style={{ maxWidth: 420, margin: '40px auto', fontFamily: 'sans-serif' }}>
<h1>Việc cần làm</h1>
<TodoForm onAdd={addTodo} />
<TodoList todos={todos} onToggle={toggleTodo} onRemove={removeTodo} />
<p>Còn {remaining} việc chưa xong</p>
</main>
);
}Điều cần nhớ: đừng sửa trực tiếp mảng state (như dùng push), hãy tạo mảng mới bằng map, filter hoặc toán tử trải .... Hàm khởi tạo lười useState(loadTodos) chỉ đọc localStorage một lần khi tải trang. useEffect với mảng phụ thuộc [todos] chạy mỗi khi danh sách thay đổi để lưu lại.
import { useState } from 'react';
export default function TodoForm({ onAdd }) {
const [text, setText] = useState('');
function handleSubmit(e) {
e.preventDefault();
const value = text.trim();
if (!value) return;
onAdd(value);
setText('');
}
return (
<form onSubmit={handleSubmit} style={{ display: 'flex', gap: 8 }}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Nhập công việc..."
style={{ flex: 1 }}
/>
<button type="submit">Thêm</button>
</form>
);
}Ô nhập này là controlled component: giá trị của nó do state điều khiển, mỗi lần gõ phím thì onChange cập nhật state.
export default function TodoList({ todos, onToggle, onRemove }) {
if (todos.length === 0) {
return <p>Chưa có công việc nào.</p>;
}
return (
<ul style={{ listStyle: 'none', padding: 0 }}>
{todos.map((todo) => (
<li key={todo.id} style={{ display: 'flex', gap: 8, padding: '6px 0' }}>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
<span
style={{
flex: 1,
textDecoration: todo.done ? 'line-through' : 'none',
}}
>
{todo.text}
</span>
<button onClick={() => onRemove(todo.id)}>Xóa</button>
</li>
))}
</ul>
);
}Thuộc tính key giúp React nhận ra phần tử nào thay đổi, thêm hay xóa. Hãy dùng một định danh ổn định như id, tránh dùng chỉ số mảng khi danh sách có thể đổi thứ tự hoặc xóa phần tử. Ở đây ta dùng style nội tuyến cho gọn trong ví dụ; dự án thật nên dùng file CSS hoặc CSS Modules.
document.createElement hay tự vẽ lại danh sách, bạn chỉ khai báo giao diện cho từng trạng thái.key trong danh sách, console sẽ cảnh báo.useEffect làm hiệu ứng chạy sau mỗi lần vẽ.Bạn đã dùng useState, useEffect, props và danh sách có key, đủ để đọc hiểu phần lớn code React cơ bản. Hãy thử thêm bộ lọc, chế độ sửa công việc, hoặc đổi sang dùng useReducer để so sánh.
Để học React cùng các kỹ thuật hiện đại một cách có hệ thống, bạn có thể xem các khóa học tại codebem.com.
Nguyễn Đình Cường
0 comment