Nếu bạn biết tới React, chắc hẳn bạn cũng đã nghe qua khái niệm "state". State chính là "bộ não" của một component, lưu trữ những dữ liệu có thể thay đổi và quyết định component sẽ hiển thị như thế nào. Trước đây, để sử dụng state, chúng ta phải dùng đến Class Component. Nhưng kể từ phiên bản React 16.8, một cuộc cách mạng đã nổ ra với sự ra đời của Hooks, và useState
chính là Hook cơ bản và quan trọng nhất, mở ra kỷ nguyên cho Functional Component.
Bài viết này sẽ giúp bạn làm chủ useState
, từ những khái niệm vỡ lòng cho đến các kỹ thuật nâng cao. Hãy cùng khám phá nhé!
useState Hook là gì? 💡
Hãy tưởng tượng Functional Component là một người có trí nhớ ngắn hạn. Mỗi khi được render lại, nó sẽ quên hết mọi thứ. useState
là một Hook (một hàm đặc biệt) cho phép bạn "cắm" một bộ nhớ vào Functional Component đó. Vả rồi, component có thể ghi nhớ, đọc và cập nhật trạng thái (state) của nó qua các lần render khác nhau mà không cần phải viết một class.
Nói một cách đơn giản, useState
mang khả năng quản lý state vào thế giới của các component dạng hàm, giúp code của bạn trở nên gọn gàng, dễ đọc và dễ bảo trì hơn rất nhiều.
Cú pháp và Cách hoạt động ✨
Để sử dụng useState
, đầu tiên bạn cần import nó từ thư viện React:
import React, { useState } from 'react'
Cú pháp cơ bản của useState
trông như sau:
const [state, setState] = useState(initialState)
Hãy cùng "mổ xẻ" từng phần một:
initialState
: Đây là giá trị khởi tạo cho state của bạn. Nó có thể là bất cứ thứ gì: một con số, một chuỗi, một boolean, một object, hay một array. React chỉ sử dụng giá trị này trong lần render đầu tiên.state
: 📦 Đây là biến chứa giá trị hiện tại của state. Bạn có thể đặt tên bất kỳ cho biến này (ví dụ:count
,userName
,isActive
).setState
: ⚙️ Đây là một hàm đặc biệt mà React cung cấp để bạn có thể cập nhật giá trị củastate
. Khi bạn gọi hàm này với một giá trị mới, React sẽ tự động lên lịch render lại component của bạn với giá trị state đã được cập nhật. Tên của hàm này thường được đặt theo quy ước làset
+ TênBiếnState (ví dụ:setCount
,setUserName
).[...]
(Array Destructuring): Đây là cú pháp của JavaScript để "bung" hai phần tử trong mảng màuseState
trả về (giá trị state và hàm cập nhật) ra thành hai biến riêng biệt.
Ví dụ kinh điển: Bộ đếm (Counter)
Đây là ví dụ "Hello, World!" của useState
sẽ giúp bạn hiểu ngay lập tức.
import React, { useState } from 'react'
function Counter() {
// 1. Khai báo một biến state mới tên là "count"
// Giá trị khởi tạo là 0
const [count, setCount] = useState(0)
return (
<div>
<p>Bạn đã click {count} lần</p>
{/* 2. Khi click, gọi hàm setCount để cập nhật state */}
<button onClick={() => setCount(count + 1)}>Click vào tôi</button>
</div>
)
}
export default Counter
Luồng hoạt động:
- Lần render đầu tiên:
useState(0)
được gọi. Biếncount
nhận giá trị là0
. Component hiển thị "Bạn đã click 0 lần". - Khi bạn click vào button: Hàm
onClick
được thực thi, gọisetCount(count + 1)
(tức làsetCount(1)
). - React lên lịch render lại: React thấy rằng state đã thay đổi, nó sẽ render lại component
Counter
. - Lần render thứ hai:
useState
được gọi lại, nhưng lần này React biết component này đã có state từ trước, nên nó trả về giá trị hiện tại là1
. Biếncount
bây giờ là1
. Component hiển thị "Bạn đã click 1 lần".
Quá trình này cứ thế lặp lại mỗi khi bạn click.
Những khái niệm nâng cao và Quy tắc vàng 🏆
Sử dụng useState
không chỉ dừng lại ở việc đếm số. Để trở thành một "cao thủ", bạn cần nắm vững những quy tắc và kỹ thuật sau.
1. Cập nhật State là Object hoặc Array
Đây là một lỗi rất phổ biến mà người mới bắt đầu thường mắc phải. Quy tắc vàng: Không bao giờ thay đổi trực tiếp (mutate) state! Bạn phải luôn tạo ra một object hoặc array mới.
Tại sao? React so sánh sự khác biệt giữa state cũ và state mới để quyết định có render lại hay không. Nếu bạn chỉ thay đổi một thuộc tính bên trong object cũ, React sẽ thấy rằng địa chỉ tham chiếu của object không đổi và có thể bỏ qua việc render lại.
Đối với Object:
const [user, setUser] = useState({ name: 'Alice', age: 25 })
// ❌ SAI: Thay đổi trực tiếp
// const handleAgeIncrease = () => {
// user.age = user.age + 1;
// setUser(user); // React có thể không render lại
// };
// ✅ ĐÚNG: Dùng spread syntax (...) để tạo object mới
const handleAgeIncrease = () => {
setUser({ ...user, age: user.age + 1 })
}
Đối với Array:
const [todos, setTodos] = useState(['Học React', 'Đi ngủ'])
// ❌ SAI: Thay đổi trực tiếp
// const addTodo = () => {
// todos.push('Làm bài tập');
// setTodos(todos); // React có thể không render lại
// };
// ✅ ĐÚNG: Dùng spread syntax (...) để tạo array mới
const addTodo = (newTodo) => {
setTodos([...todos, newTodo])
}
2. Cập nhật State dựa trên State trước đó
Hãy tưởng tượng bạn có một nút bấm tăng giá trị lên 2 lần liên tiếp:
const [count, setCount] = useState(0)
const handleIncreaseByTwo = () => {
setCount(count + 1) // count ở đây vẫn là 0
setCount(count + 1) // count ở đây vẫn là 0
}
// Kết quả: count chỉ tăng lên 1, không phải 2!
Vấn đề này xảy ra vì việc cập nhật state trong React là bất đồng bộ. React có thể "gom" nhiều lần gọi setCount
lại để tối ưu hiệu suất. Cả hai lần gọi setCount
đều đọc giá trị count
là 0
.
Giải pháp: Truyền một hàm vào bên trong setState
. Hàm này sẽ nhận vào state trước đó (prevState
) làm tham số và trả về giá trị state mới. Đây là cách an toàn và được khuyên dùng khi state mới phụ thuộc vào state cũ.
const handleIncreaseByTwoCorrectly = () => {
setCount((prevCount) => prevCount + 1)
setCount((prevCount) => prevCount + 1)
}
// Kết quả: count sẽ tăng lên 2!
3. Quy tắc vàng của Hooks ⚠️
React đặt ra 2 quy tắc bắt buộc khi sử dụng Hooks mà bạn phải tuân thủ:
- Chỉ gọi Hooks ở cấp cao nhất (Top Level): Không gọi Hooks bên trong các vòng lặp, câu điều kiện, hay các hàm lồng nhau.
- Lý do: React dựa vào thứ tự các Hook được gọi để xác định đúng state cho mỗi Hook.
- Chỉ gọi Hooks từ React Functional Components: Không gọi Hooks từ các hàm JavaScript thông thường.
Ví dụ thực tế: Form đăng nhập đơn giản
Hãy áp dụng những gì đã học để xây dựng một form đăng nhập đơn giản, quản lý state cho cả username và password.
import React, { useState } from 'react'
function LoginForm() {
const [formData, setFormData] = useState({
username: '',
password: '',
})
const handleChange = (event) => {
const { name, value } = event.target
// Dùng functional update và spread syntax để cập nhật an toàn
setFormData((prevData) => ({
...prevData,
[name]: value, // Sử dụng computed property name
}))
}
const handleSubmit = (event) => {
event.preventDefault() // Ngăn trình duyệt reload
alert(
`Đăng nhập với Username: ${formData.username} và Password: ${formData.password}`,
)
}
return (
<form onSubmit={handleSubmit}>
<label>
Username:
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
/>
</label>
<br />
<label>
Password:
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
</label>
<br />
<button type="submit">Đăng nhập</button>
</form>
)
}
Trong ví dụ này, chúng ta đã sử dụng một state duy nhất là một object để quản lý toàn bộ dữ liệu của form, một kỹ thuật rất phổ biến và hiệu quả.
Kết luận: useState là nền tảng của ứng dụng React
useState
là một Hook đơn giản về mặt cú pháp nhưng lại vô cùng mạnh mẽ, là viên gạch nền tảng xây dựng nên các ứng dụng React hiện đại. Bằng cách hiểu rõ cách nó hoạt động, các quy tắc sử dụng và những mẫu phổ biến, bạn đã có trong tay một công cụ tuyệt vời để tạo ra các component linh hoạt và dễ dàng quản lý.
Từ đây, bạn đã sẵn sàng để khám phá các Hook khác như useEffect
để xử lý side effects, useContext
để quản lý state toàn cục, và nhiều hơn nữa.
Chúc mừng bạn đã đi qua bước đầu tiên của React Hooks!