Series 4 – React Hooks – State

 

Bài 1 : Hooks, useState và useEffect – Quản lý dữ liệu và side effects trong React

1. React Hooks là gì?

Hooks là những hàm do React cung cấp để Function Component sử dụng các khả năng như quản lý state, đồng bộ với hệ thống bên ngoài và chia sẻ logic giữa các component.

Ba khái niệm cần hiểu đầu tiên là Hooks, useState và useEffect.

2. Hooks

Một số Hook phổ biến:

Hook Công dụng
useState Quản lý state
useEffect Đồng bộ component với hệ thống bên ngoài
useRef Lưu giá trị không cần kích hoạt render
useContext Đọc dữ liệu từ Context
useMemo Ghi nhớ kết quả tính toán khi phù hợp
useCallback Ghi nhớ hàm khi phù hợp

Quy tắc sử dụng Hooks

  1. Gọi Hooks ở cấp cao nhất của Function Component hoặc custom Hook.
  2. Không gọi Hooks bên trong vòng lặp, điều kiện hoặc hàm lồng nhau.
  3. Không gọi Hooks trực tiếp từ một hàm thông thường nằm ngoài component hay custom Hook.

Ví dụ đúng:

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

Việc tuân thủ quy tắc này giúp React giữ đúng thứ tự các Hook giữa những lần render.

3. useState

useState cho phép component lưu dữ liệu thay đổi theo thời gian.

import { useState } from "react";

function Toggle() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <button onClick={() => setIsOpen((previous) => !previous)}>
      {isOpen ? "Đang mở" : "Đang đóng"}
    </button>
  );
}

Khi gọi hàm cập nhật state, React lên lịch render lại component với state mới.

State có thể là số, chuỗi, Boolean, mảng hoặc object. Khi cập nhật mảng hay object, nên tạo giá trị mới thay vì sửa trực tiếp dữ liệu hiện tại.

4. useEffect

useEffect được sử dụng để đồng bộ component với hệ thống bên ngoài, chẳng hạn:

  • Đăng ký hoặc hủy đăng ký sự kiện.
  • Kết nối đến một dịch vụ bên ngoài.
  • Đồng bộ với một API hoặc thư viện không thuộc React.
  • Quản lý một số side effects theo vòng đời component.

Ví dụ đăng ký sự kiện online:

import { useEffect, useState } from "react";

function NetworkStatus() {
  const [isOnline, setIsOnline] = useState(
    typeof navigator !== "undefined"
      ? navigator.onLine
      : true
  );

  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }

    function handleOffline() {
      setIsOnline(false);
    }

    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);

    return () => {
      window.removeEventListener("online", handleOnline);
      window.removeEventListener("offline", handleOffline);
    };
  }, []);

  return <p>{isOnline ? "Đang trực tuyến" : "Mất kết nối"}</p>;
}

Trong ví dụ:

  • Effect đăng ký hai sự kiện khi được thiết lập.
  • Hàm cleanup gỡ các sự kiện khi Effect cần được dọn dẹp.
  • Mảng dependency rỗng thể hiện Effect không phụ thuộc vào giá trị reactive nào từ component. Trong môi trường phát triển với Strict Mode, React có thể chạy thêm một chu kỳ thiết lập và dọn dẹp để giúp phát hiện lỗi.

Dependency array hoạt động như thế nào?

useEffect(() => {
  // Logic đồng bộ
}, [value]);

Effect sẽ được thiết lập lại khi dependency thay đổi.

  • Không truyền dependency array: Effect chạy sau mỗi lần component commit.
  • Truyền []: Effect không phụ thuộc vào các giá trị reactive từ component.
  • Truyền [value]: Effect đồng bộ lại khi value thay đổi.

Không nên dùng useEffect cho mọi phép tính. Nếu chỉ cần tính một giá trị từ props hoặc state, hãy tính trực tiếp trong lúc render khi phép tính đủ đơn giản.

5. So sánh useState và useEffect

Tiêu chí useState useEffect
Mục đích Lưu dữ liệu Đồng bộ với hệ thống bên ngoài
Khi dùng Dữ liệu ảnh hưởng giao diện Side effects cần đồng bộ
Ví dụ Bộ đếm, trạng thái modal Event listener, kết nối bên ngoài

6. Bài tập thực hành

Xây dựng component hiển thị bộ đếm thời gian:

  1. Sử dụng useState để lưu số giây.
  2. Sử dụng useEffect để thiết lập bộ đếm bằng setInterval.
  3. Tăng số giây mỗi giây.
  4. Dọn dẹp interval khi Effect kết thúc.
  5. Đảm bảo không tạo ra nhiều interval ngoài ý muốn.

7. Kết luận

Hooks giúp Function Component quản lý state và tương tác với hệ thống bên ngoài. useState phục vụ dữ liệu của giao diện, còn useEffect được dùng khi cần đồng bộ với những hệ thống nằm ngoài quá trình render của React.

Tiếp theo, chúng ta sẽ tìm hiểu useRef, Context API và State Lifting để xử lý việc lưu trữ và chia sẻ dữ liệu giữa nhiều component.

 

 

 

Bài 2 :useRef, Context API và State Lifting – Lưu trữ và chia sẻ dữ liệu giữa các component

 

1. Mở đầu

Khi ứng dụng React phát triển, dữ liệu không chỉ nằm trong một component. Chúng ta có thể cần truy cập trực tiếp vào một input, chia sẻ thông tin đăng nhập hoặc đồng bộ trạng thái giữa các component.

Ba khái niệm useRef, Context API và State Lifting giúp giải quyết những nhu cầu này.

2. useRef

useRef tạo ra một object có thuộc tính current, có thể giữ giá trị giữa các lần render mà việc thay đổi giá trị đó không tự kích hoạt render.

Truy cập DOM

import { useRef } from "react";

function SearchBox() {
  const inputRef = useRef(null);

  function handleFocus() {
    inputRef.current?.focus();
  }

  return (
    <div>
      <input ref={inputRef} placeholder="Nhập từ khóa" />
      <button onClick={handleFocus}>Tìm kiếm</button>
    </div>
  );
}

Khi nhấn nút, inputRef.current tham chiếu đến phần tử input và gọi phương thức focus().

Lưu giá trị không cần render lại

const clickCountRef = useRef(0);

function handleClick() {
  clickCountRef.current += 1;
}

Thay đổi clickCountRef.current không khiến component render lại. Vì vậy, nếu muốn hiển thị số lượt nhấn trên giao diện, nên dùng state thay vì chỉ dùng ref.

Ghi nhớ: State dùng cho dữ liệu cần phản ánh lên giao diện; ref phù hợp với tham chiếu DOM hoặc giá trị nội bộ không cần tự kích hoạt render.

3. Context API

Context cho phép cung cấp dữ liệu cho các component ở sâu trong cây component mà không cần truyền props qua từng cấp trung gian.

Ví dụ, ứng dụng có thể chia sẻ giao diện sáng/tối.

import { createContext, useContext, useState } from "react";

const ThemeContext = createContext("light");

function ThemeLabel() {
  const theme = useContext(ThemeContext);

  return <p>Giao diện hiện tại: {theme}</p>;
}

function App() {
  const [theme, setTheme] = useState("light");

  return (
    <ThemeContext.Provider value={theme}>
      <ThemeLabel />

      <button
        onClick={() =>
          setTheme((previous) =>
            previous === "light" ? "dark" : "light"
          )
        }
      >
        Đổi giao diện
      </button>
    </ThemeContext.Provider>
  );
}

export default App;

Trong ví dụ này:

  • createContext() tạo Context.
  • Provider cung cấp giá trị cho các component con.
  • useContext() đọc giá trị Context gần nhất từ cây component.

Khi giá trị Context thay đổi, những component sử dụng giá trị đó có thể render lại.

Context hữu ích cho theme, ngôn ngữ hoặc thông tin người dùng dùng chung. Tuy nhiên, không nên đưa tất cả state vào Context một cách tùy tiện. Nếu dữ liệu chỉ được sử dụng trong một khu vực nhỏ, props hoặc state cục bộ có thể đơn giản hơn.

4. State Lifting

State Lifting là việc đưa state từ một component con lên component cha chung gần nhất khi nhiều component cần dùng hoặc cập nhật cùng một dữ liệu.

Ví dụ, một component chọn số lượng và một component hiển thị tổng số lượng cần cùng sử dụng một giá trị.

import { useState } from "react";

function QuantityPicker({ quantity, onChange }) {
  return (
    <button onClick={() => onChange(quantity + 1)}>
      Tăng số lượng: {quantity}
    </button>
  );
}

function QuantitySummary({ quantity }) {
  return <p>Tổng số lượng: {quantity}</p>;
}

function App() {
  const [quantity, setQuantity] = useState(1);

  return (
    <div>
      <QuantityPicker
        quantity={quantity}
        onChange={setQuantity}
      />
      <QuantitySummary quantity={quantity} />
    </div>
  );
}

State quantity được đặt trong App, sau đó truyền xuống hai component con.

Cách này giúp dữ liệu có một nguồn sự thật thống nhất (single source of truth) trong phạm vi các component cần chia sẻ nó.

5. So sánh ba khái niệm

Khái niệm Mục đích chính Ví dụ
useRef Lưu tham chiếu hoặc giá trị không cần render Focus input
Context API Cung cấp dữ liệu cho nhiều cấp component Theme, ngôn ngữ
State Lifting Đưa state lên cha chung để chia sẻ Bộ chọn số lượng

6. Bài tập thực hành

Xây dựng một trang cài đặt gồm:

  1. Input nhập tên người dùng.
  2. Nút focus input sử dụng useRef.
  3. State lưu tên và hiển thị lời chào.
  4. Context chia sẻ chế độ sáng/tối cho các component con.
  5. Một component khác hiển thị cùng tên người dùng thông qua props hoặc Context.

Hãy cân nhắc dữ liệu nào nên nằm trong state cục bộ, dữ liệu nào cần chia sẻ và khi nào useRef là lựa chọn phù hợp.

7. Kết luận

useRef, Context API và State Lifting giải quyết ba nhu cầu khác nhau: tham chiếu, chia sẻ dữ liệu xuyên cây component và đồng bộ state giữa các component liên quan.

Bài tiếp theo sẽ tập trung vào cách các component giao tiếp với nhau, sử dụng props và tổ chức state khi ứng dụng có nhiều thành phần.

Bài 3 :Component Communication, Props và State Sharing – Cách tổ chức dữ liệu giữa các component React

 

1. Mở đầu

Trong một dự án React, component thường được chia nhỏ để dễ tái sử dụng và bảo trì. Tuy nhiên, khi nhiều component cần trao đổi dữ liệu, chúng ta cần lựa chọn cách truyền dữ liệu phù hợp.

Ví dụ, trong website bán hàng:

  • ProductList hiển thị danh sách sản phẩm.
  • ProductCard hiển thị thông tin từng sản phẩm.
  • Cart hiển thị những sản phẩm được chọn.
  • Header hiển thị số lượng sản phẩm trong giỏ hàng.

Các component này cần phối hợp để tạo thành một ứng dụng thống nhất.

2. Component Communication

Component Communication là cách các component trao đổi dữ liệu hoặc thông báo cho nhau.

Trong React, dữ liệu thường được truyền từ component cha xuống component con. Khi component con cần yêu cầu component cha thực hiện một hành động, cha có thể truyền xuống một callback.

Ví dụ:

function Child({ onNotify }) {
  return (
    <button onClick={() => onNotify("Xin chào từ Child")}>
      Gửi thông báo
    </button>
  );
}

function Parent() {
  function handleNotify(message) {
    alert(message);
  }

  return <Child onNotify={handleNotify} />;
}

Ở đây, Child không trực tiếp điều khiển logic của Parent. Nó gọi callback mà cha truyền xuống.

Đây là một cách phổ biến để component con gửi thông tin hoặc yêu cầu xử lý lên component cha.

3. Props

Props là dữ liệu mà component nhận từ bên ngoài, thường là từ component cha.

function ProductCard({ name, price }) {
  return (
    <article>
      <h2>{name}</h2>
      <p>{price} VNĐ</p>
    </article>
  );
}

function ProductList() {
  return (
    <ProductCard
      name="Keyboard"
      price={500000}
    />
  );
}

Props có thể chứa những gì?

Props có thể là:

  • Chuỗi, số hoặc Boolean.
  • Mảng hoặc object.
  • Component hoặc JSX.
  • Hàm callback để component con thông báo lại cho cha.

Props được xem là dữ liệu chỉ đọc từ góc nhìn của component nhận chúng. Nếu cần thay đổi dữ liệu, component nên gọi callback do cha cung cấp thay vì sửa trực tiếp props.

4. State Sharing

State Sharing là việc tổ chức state sao cho các component cần cùng một dữ liệu đều có thể truy cập hoặc cập nhật dữ liệu đó.

Một nguyên tắc quan trọng là đặt state tại component chung gần nhất có thể quản lý nhu cầu sử dụng dữ liệu của các component liên quan.

Ví dụ, giỏ hàng cần được dùng bởi cả danh sách sản phẩm và phần hiển thị số lượng trên header.

import { useState } from "react";

function Product({ onAdd }) {
  return (
    <button onClick={() => onAdd("Keyboard")}>
      Thêm Keyboard vào giỏ
    </button>
  );
}

function Header({ cart }) {
  return <p>Số sản phẩm: {cart.length}</p>;
}

function App() {
  const [cart, setCart] = useState([]);

  function handleAdd(product) {
    setCart((previous) => [...previous, product]);
  }

  return (
    <div>
      <Header cart={cart} />
      <Product onAdd={handleAdd} />
    </div>
  );
}

export default App;

Khi người dùng nhấn nút, Product gọi callback. App cập nhật state cart, sau đó React cập nhật những component phụ thuộc vào dữ liệu mới.

Ví dụ trên minh họa cơ chế cơ bản. Trong ứng dụng bán hàng thực tế, mỗi sản phẩm nên có ID và số lượng riêng, đồng thời cần xử lý việc tăng số lượng thay vì luôn thêm một mục mới.

5. Khi nào dùng props, State Lifting hoặc Context?

Trường hợp Cách tiếp cận
Truyền dữ liệu cho component con trực tiếp Props
Component con cần yêu cầu cha cập nhật dữ liệu Callback qua props
Nhiều component anh em cần cùng state State Lifting
Nhiều cấp component cần dữ liệu dùng chung Context API
State phức tạp, nhiều thao tác hoặc nhiều vùng sử dụng Cân nhắc reducer hoặc thư viện quản lý state

Không phải ứng dụng nào cũng cần thư viện quản lý state toàn cục. Với ứng dụng nhỏ, state cục bộ, props và Context thường đã đủ.

6. Những lỗi thường gặp

  • Đưa state lên quá cao dù chỉ một component cần sử dụng.
  • Truyền quá nhiều props qua nhiều tầng trung gian khiến code khó bảo trì.
  • Sửa trực tiếp props hoặc state.
  • Dùng Context cho mọi dữ liệu, kể cả những dữ liệu chỉ cần ở một component.
  • Không xác định rõ component nào chịu trách nhiệm quản lý dữ liệu.

7. Bài tập thực hành

Xây dựng một ứng dụng giỏ hàng đơn giản:

  1. Component ProductList hiển thị ba sản phẩm.
  2. Component ProductCard có nút thêm sản phẩm.
  3. Component Header hiển thị tổng số lượng sản phẩm.
  4. State giỏ hàng được quản lý tại component cha chung.
  5. Dữ liệu và callback được truyền bằng props.

Sau khi hoàn thành, hãy thử thêm một component khác hiển thị tổng giá trị giỏ hàng và xác định nơi thích hợp để quản lý dữ liệu.

8. Kết luận

Component Communication, Props và State Sharing giúp các component phối hợp với nhau mà vẫn giữ được cấu trúc rõ ràng.

Khi biết cách truyền dữ liệu và xác định nơi quản lý state, bạn sẽ dễ dàng mở rộng ứng dụng mà không khiến các component phụ thuộc quá chặt chẽ vào nhau.

Tiếp theo, chúng ta sẽ tìm hiểu cách React render component và những yếu tố khiến component render lại khi dữ liệu thay đổi.

Bài viết khác

Series 7 — Web Quality – Engineering :Testing – Unit Testing, Integration Testing, Web Performance – Core Web Vitals, Code Splitting, Lazy Loading

Series 7 — Web Quality – Engineering Bài 3: Testing – Unit Testing, Integration Testing và End-to-End Testing trong ứng dụng web 1. Vì sao ứng dụng web cần Testing? Trong quá trình phát triển Frontend, chúng ta thường kiểm tra ứng dụng bằng cách chạy website trên trình duyệt, nhấn vào các nút, nhập […]

Series 7 — Web Quality – Engineering : Authentication & Authorization

Series 7 — Web Quality – Engineering Bài 1: Authentication & Authorization – Xác thực và phân quyền trong ứng dụng web 1. Mở đầu Khi xây dựng một ứng dụng web có chức năng đăng nhập, chúng ta thường nghĩ rằng chỉ cần tạo biểu mẫu nhập email, mật khẩu và kiểm tra thông […]

Series 6 – Next.js Advanced – Production: Next.js Build & Deployment

Series 6 – Next.js Advanced & Production Bài 5: Next.js Build & Deployment – Từ mã nguồn đến môi trường Production 1. Mở đầu Trong quá trình phát triển một ứng dụng web, việc chạy thành công trên máy tính cá nhân chưa có nghĩa là ứng dụng đã sẵn sàng để đưa đến người […]

Series 6 – Next.js Advanced – Production: Next.js Internationalized Routing Next.js Static Export

Series 6 – Next.js Advanced & Production   Bài 3: Next.js Internationalized Routing – Tổ chức URL cho website đa ngôn ngữ 1. Mở đầu Sau khi xây dựng cơ chế dịch nội dung và định dạng theo locale, ứng dụng cần một cách để người dùng truy cập đúng phiên bản ngôn ngữ. Ví […]

Series 6 – Next.js Advanced & Production: Metadata & SEO, Internationalization (i18n) & Localization (l10n)

Series 6 – Next.js Advanced & Production Bài 1: Metadata & SEO trong Next.js – Tối ưu thông tin và khả năng hiển thị trên công cụ tìm kiếm 1. Mở đầu Khi xây dựng một website, việc hoàn thiện giao diện và chức năng mới chỉ là một phần của quá trình phát triển. […]

Series 5 – Next.js Fundamentals: CSR, SSR, SSG, ISR và cách lựa chọn

Series 5 – Next.js Fundamentals   Bài 7 : CSR, SSR, SSG và ISR – Các chiến lược Rendering trong Next.js 1. Mở đầu Khi xây dựng một website bằng React, chúng ta thường tập trung vào việc tạo component, quản lý state và hiển thị giao diện trên trình duyệt. Tuy nhiên, khi ứng dụng […]

Leave a Reply

Your email address will not be published. Required fields are marked *