[React Basics] useState Hook: Cách quản lý State hiệu quả trong React

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.

useState Hook: Cách quản lý State hiệu quả trong React

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ủa state. 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:

  1. Lần render đầu tiên: useState(0) được gọi. Biến count nhận giá trị là 0. Component hiển thị "Bạn đã click 0 lần".
  2. Khi bạn click vào button: Hàm onClick được thực thi, gọi setCount(count + 1) (tức là setCount(1)).
  3. React lên lịch render lại: React thấy rằng state đã thay đổi, nó sẽ render lại component Counter.
  4. 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ến count 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ị count0.

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ủ:

  1. 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.
  2. 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!

Bài viết liên quan

[React Basics] Quản lý State phức tạp với useReducer Hook trong React

Tìm hiểu useReducer hook - giải pháp mạnh mẽ cho việc quản lý state phức tạp trong React. Bài viết này sẽ giúp bạn hiểu rõ nguyên lý hoạt động và cách áp dụng hiệu quả.

[React Basics] Stateful và Stateless Components: Phân biệt và Ứng dụng trong React

Sự khác biệt cốt lõi giữa Stateful và Stateless Components trong React là gì? Tìm hiểu khi nào và tại sao nên sử dụng từng loại để tối ưu hiệu suất ứng dụng của bạn.

[React Basics] useEffect Hook: Hiểu sâu về dependencies và vòng đời component

Bạn đã thực sự hiểu về useEffect? Tìm hiểu sâu hơn về cơ chế hoạt động, vai trò của dependencies array và cách useEffect mô phỏng các phương thức trong vòng đời của class component.

[React Basics] useCallback và useMemo: Hiểu rõ cách tối ưu hiệu năng React App

Tìm hiểu sự khác biệt giữa useCallback và useMemo trong React. Bài viết này giải thích chi tiết cách chúng hoạt động và khi nào nên sử dụng để tối ưu hiệu năng ứng dụng của bạn.