React là một trong những thư viện phổ biến nhất để xây dựng giao diện người dùng động. Tuy nhiên, chính sự linh hoạt này cũng dẫn đến việc các nhà phát triển, đặc biệt là những người mới, dễ mắc phải một số lỗi cơ bản.
Trong bài viết này, chúng ta sẽ cùng khám phá 5 lỗi thường gặp khi phát triển React và tìm hiểu cách khắc phục để cải thiện chất lượng mã và hiệu suất ứng dụng.
1. Thay đổi trực tiếp State (Mutating State)
Hãy xem xét một ví dụ đơn giản: một React component hiển thị danh sách các mục và cho phép thêm hoặc xóa mục:
import { useState } from "react";
const Home = () => {
const [items, setItems] = useState(['item1', 'item2']);
const [itemToAdd, setItemToAdd] = useState('');
function wrongHandleAddItem(item) {
items.push(item);
setItems(items);
}
function goodHandleAddItem(item) {
if (item.length === 0) return;
const newArray = [...items, item];
setItems(newArray);
setItemToAdd('');
}
function removeItem(item) {
const itemIndex = items.indexOf(item);
if (itemIndex !== -1) {
const newArray = [...items];
newArray.splice(itemIndex, 1);
setItems(newArray);
}
}
return (
<div style={{ padding: '3rem', maxWidth: '12rem' }}>
<div>
{items.map((item, key) => (
<div key={key} style={{ marginTop: '0.5rem', display: 'flex', justifyContent: 'space-between' }}>
{item}
<button onClick={() => removeItem(item)}>-</button>
</div>
))}
</div>
<div style={{ marginTop: '1rem' }}>
<input value={itemToAdd} onChange={event => setItemToAdd(event.target.value)} />
<button onClick={() => wrongHandleAddItem(itemToAdd)} style={{ marginLeft: '1rem' }}>+</button>
</div>
</div>
);
};
export default Home;
Phân tích
- Trong hàm wrongHandleAddItem, chúng ta sử dụng phương thức push để thêm mục mới vào mảng items. Tuy nhiên, hành động này thay đổi trực tiếp mảng. Sau đó, khi gọi setItems(items), React không nhận diện được sự thay đổi, do đó không cập nhật lại giao diện.
- Lý do: React dựa vào identity (định danh) của biến state để xác định khi nào cần cập nhật. Thay đổi nội dung mà không thay đổi định danh của mảng sẽ không kích hoạt re-render.
Cách sửa
Sử dụng toán tử spread (…) để tạo một mảng mới, sau đó cập nhật state:
function goodHandleAddItem(item) {
if (item.length === 0) return;
const newArray = [...items, item];
setItems(newArray);
setItemToAdd('');
}
2. Không sử dụng key trong danh sách
React yêu cầu mỗi phần tử trong một danh sách phải có một key duy nhất. Ví dụ:
items.map((item) => (
<div>
{item}
<button onClick={() => removeItem(item)}>-</button>
</div>
));
Lỗi
Khi không có key, React không thể theo dõi chính xác từng phần tử trong danh sách, dẫn đến các hành vi không mong muốn khi thêm, xóa hoặc cập nhật danh sách.
Cách sửa
- Sử dụng chỉ số (index) làm key:
items.map((item, index) => (
<div key={index}>
{item}
<button onClick={() => removeItem(item)}>-</button>
</div>
));
- Sử dụng giá trị id duy nhất:
const newItem = { id: crypto.randomUUID(), value: item };
const newArray = [...items, newItem];
setItems(newArray);
items.map((item) => (
<div key={item.id}>
{item.value}
<button onClick={() => removeItem(item)}>-</button>
</div>
));
3. Dùng async trong useEffect
Khi gọi API trong useEffect, chúng ta cần xử lý các thao tác bất đồng bộ một cách cẩn thận. Ví dụ:
useEffect(async () => {
const res = await fetch("/api/to/fetch");
const data = await res.json();
console.log(data);
}, []);
Lỗi
useEffect không chấp nhận các hàm bất đồng bộ (async). Kết quả là nhận được lỗi:
destroy is not a function
Cách sửa
Tách logic bất đồng bộ vào một hàm khác bên trong useEffect:
useEffect(() => {
async function fetchData() {
const res = await fetch("/api/to/fetch");
const data = await res.json();
console.log(data);
}
fetchData();
}, []);
4. Truy cập state trước khi re-render
Cập nhật state trong React là bất đồng bộ. Điều này có thể dẫn đến lỗi khi cố gắng truy cập giá trị mới của state ngay sau khi gọi hàm setState.
Ví dụ sai
setItems([...items, newItem]);
console.log(items); // Giá trị cũ của items sẽ được in ra
Cách sửa
Sử dụng giá trị đã biết, thay vì dựa vào state chưa được cập nhật:
const updatedItems = [...items, newItem];
setItems(updatedItems);
console.log(updatedItems);
5. Sử dụng state cũ (stale state)
Một vấn đề phổ biến khác là sử dụng giá trị cũ của state trong các cập nhật liên tiếp:
setCount(count + 1);
setCount(count + 1);
Lỗi
Do React gộp các cập nhật state lại, cả hai lần gọi đều sử dụng giá trị cũ của count.
Cách sửa
Sử dụng hàm callback của setState để đảm bảo giá trị mới được cập nhật dựa trên giá trị trước đó:
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
Kết luận
Tránh những lỗi này không chỉ giúp bạn viết mã React tốt hơn mà còn tăng hiệu suất ứng dụng đáng kể.
Hy vọng bài viết này hữu ích cho bạn. Chúc bạn học React vui vẻ và thành công!
Bạn có gặp lỗi nào khác? Hãy chia sẻ cùng mọi người nhé!