Khi sử dụng state, ta cần tuân thủ theo những nguyên lý sau:
Group Related State
Nếu ta thường cập nhật một hoặc nhiều state cùng một lúc, ta nên gộp chúng lại.
Ví dụ, thay vì dùng hai state như sau:
const [x, setX] = useState(0)
const [y, setY] = useState(0)Ta có thể gộp chúng lại thành:
const [position, setPosition] = useState({ x: 0, y: 0 })Khi đó, mỗi lần có sự kiện xảy ra, ta chỉ cần cập nhật một state là đủ.
Ta cũng nên gộp state trong trường hợp không biết số lượng chính xác của các trường thông tin, chẳng hạn như khi ứng dụng có một form cho phép người dùng tạo ra các trường custom.
Avoid Contradictions in State
Xét component sau:
function FeedbackForm() {
const [text, setText] = useState("")
const [isSending, setIsSending] = useState(false)
const [isSent, setIsSent] = useState(false)
async function handleSubmit(e) {
e.preventDefault()
setIsSending(true)
await sendMessage(text)
setIsSending(false)
setIsSent(true)
}
// ...
}Có thể thấy, state isSending và isSent không bao giờ được bằng nhau bởi vì nếu bằng nhau thì sẽ gây ra mâu thuẫn. Dẫn đến, ta luôn phải cập nhật hai state này cùng một lúc và điều này tạo ra sự phức tạp không cần thiết.
Để đơn giản hóa, ta có thể dùng một state status giúp lưu trạng thái như sau:
function FeedbackForm() {
const [text, setText] = useState("")
const [status, setStatus] = useState("typing")
async function handleSubmit(e) {
e.preventDefault()
setStatus("sending")
await sendMessage(text)
setStatus("sent")
}
const isSending = status === "sending"
const isSent = status === "sent"
// ...
}Với isSending và isSent lúc này là hai constant giúp code dễ đọc hơn.
Avoid Redundant State
Chúng ta không nên dùng những giá trị có thể tính toán được làm state.
Ví dụ, trong component bên dưới, state fullName hoàn toàn có thể được tính toán từ firstName và lastName trong quá trình render nên ta không cần dùng nó làm state.
function Form() {
const [firstName, setFirstName] = useState("")
const [lastName, setLastName] = useState("")
const [fullName, setFullName] = useState("")
function handleFirstNameChange(e) {
setFirstName(e.target.value)
setFullName(e.target.value + " " + lastName)
}
function handleLastNameChange(e) {
setLastName(e.target.value)
setFullName(firstName + " " + e.target.value)
}
// ...
}Thay vào đó, ta có thể dùng một biến thông thường để lưu giá trị tính toán ở mỗi lần render khi firstName hoặc lastName bị thay đổi như sau:
const [firstName, setFirstName] = useState("")
const [lastName, setLastName] = useState("")
const fullName = firstName + " " + lastNameAvoid Duplication in State
Giả sử xét hai state sau:
const [items, setItems] = useState(initialItems)
const [selectedItem, setSelectedItem] = useState(items[0])Ở đây xảy ra một sự trùng lắp dữ liệu: selectedItem sẽ lưu một trong số các item của state items. Dữ liệu bị trùng lặp có thể có dạng như sau:
items = [{ id: 0, title: 'pretzels'}, ...]selectedItem = {id: 0, title: 'pretzels'}
Mỗi khi selectedItem bị thay đổi, ta cần phải cập nhật một object giống với nó ở trong items. Điều này làm mất tính “single source of truth” của dữ liệu.
Thay vào đó, ta chỉ nên dùng id của item được chọn làm state và lấy ra item được chọn dựa trên các biến state:
const [items, setItems] = useState(initialItems)
const [selectedId, setSelectedId] = useState(0)
const selectedItem = items.find((item) => item.id === selectedId)Khi đó, dữ liệu được lưu trong state sẽ có dạng như sau:
items = [{ id: 0, title: 'pretzels'}, ...]selectedId = 0